Posts mit dem Label DragonflyBSD werden angezeigt. Alle Posts anzeigen
Posts mit dem Label DragonflyBSD werden angezeigt. Alle Posts anzeigen

Sonntag, 6. September 2009

Bug: Snow Leopard's dynamic_cast und explizit instanziierte C++ Templates

Derzeit arbeite ich an einem Projekt welches ich auf dem Mac (allerdings nicht für den Mac) entwickle. Dabei verwende ich unter anderem auch gerne die RTTI (RunTime Type Information) Fähigkeiten von C++ die mir unter anderem auch sichere Downcasts zur Verfügung stellen (Ist ein Downcast nicht möglich wird der Nullpointer zurückgegeben). Das alles klappt durchaus gut, es sei denn ich schreibe eine Lib in der ich bereits die Templates instanziiere die ich später in meiner Applikation nutzen will. Alles kompiliert zwar wunderbar und das Template lässt sich ebenfalls nutzen sobald ich aber auf Polymorphie zurückgreife wird es hässlich, da anscheinend die Typinformationen verloren gehen und dynamic_cast nicht mehr in der Lage ist richtig zu arbeiten.

Der ganze Fehler lässt sich mit folgendem Testfall reproduzieren:


//TestLib.hpp
class Base {
public:
virtual ~Base() {}
};

template <typename T>
class Derived : public Base {
public:
Derived();
};

/*TestLib.cpp
* compile with: g++ -dynamiclib TestLib.cpp -o libTestLib.dylib
*/
#include "TestLib.hpp"
#include <typeinfo>
#include <iostream>

template <typename T>
Derived<T>::Derived() {
std::cout << typeid(*this).name() << std::endl;
}

template class Derived<int>;

Wie man in der letzten Zeile sieht wird das Template Derived explizit mit int in TestLib.cpp instanziiert. Diesen Typen werden wir jetzt in einer kleinen Anwendung etwas durch die Typhierarchie scheuchen und ein sehr seltsames Verhalten beobachten.

/* main.cpp
* compile with: g++ -L. -lTestLib main.cpp -o main
*/
#include "TestLib.hpp"
#include <iostream>

int main() {
Derived<int>* i = new Derived<int>();
std::cout << typeid(*i).name() << std::endl;
Base *o = i;
std::cout << typeid(*o).name() << std::endl;
std::cout << o << std::endl;
std::cout << dynamic_cast< Derived<int>* >(o) << std::endl;
std::cout << typeid(*o).name() << std::endl;
delete i;
}

Wir erhalten folgende Ausgabe:

7DerivedIiE
7DerivedIiE
0x100100080
0
7DerivedIiE

Die Typid verändert sich nicht und trotzdem liefert ein gültiger Downcast den Nullpointer zurück. Ich habe den Code ebenfalls unter FreeBSD und DragonflyBSD getestet, in beiden Fällen war der Downcast erfolgreich. Meines Wissens tritt dieses Verhalten erst seit Snow Leopard auf, aber da ich derzeit nirgends eine aktuelle Version von Leopard laufen habe kann ich nicht testen ob es sich hier um einen neuen Bug handelt.

Der einzige Workaround der mir derzeit einfällt ist die Instanziierung des Templates in die Compilezeit der Anwendung zu verlegen.

UPDATE: Mal ein delete nachgebessert, nicht das sich hier noch jemand beschwert das mein Testcase leakt :P