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

2 Kommentare:

stingelin{( at }) mac.com hat gesagt…

Leider kann ich das Problem bestätigen. Unter 10.5 bestand dies noch nicht. Ich musste meinen Code umstellen und alles in eine Bibliothek packen - leider.

Das Problem ist unabhängig vom Compiler ob Apple's 4.2 oder der neuste gcc 4.4.1.

Bin selber an einer Lösung sehr interessiert.

Beste Grüsse,
S. Stingelin.

raichoo hat gesagt…

Das Problem scheint im GCC Code zu liegen. Ich hab mal nachgeforscht und mir wurde ein Link zum Bugtracker von GCC gegeben. Habe ihn aber leider verschlampt. Leider habe ich auch keine wirkliche Lösung für das Problem außer halt Code zu verschieben oder das Design verändern und auf solche Instanziierungen zu verzichten. Mich würde interessieren was andere Compiler in dem Fall machen.

Gruß
raichoo