Problem mit Visual C++ und statischer Klassenvariable
-
Hi,
ich habe den Fall auf ein minimum runtergebrochen:#include <iostream> template<typename scal_type, const unsigned row_dim, const unsigned col_dim> class mat { public: typedef scal_type value_type; public: // data definition scal_type data_array[row_dim * col_dim]; }; // class mat<scal_type, row_dim, col_dim> template<typename scal_type> class mat<scal_type, 2, 2> { public: typedef scal_type value_type; public: // ctors mat(); mat(const mat<scal_type, 2, 2>& m); explicit mat(const scal_type a00, const scal_type a01, const scal_type a02, const scal_type a03); // constants static const mat<scal_type, 2, 2> identity; // data definition union { struct { scal_type m00; scal_type m01; scal_type m02; scal_type m03; }; scal_type data_array[4]; }; }; // class mat<scal_type, 4, 4> template<typename scal_type> inline mat<scal_type, 2, 2>::mat(const mat<scal_type, 2, 2>& m) : m00(m.m00), m01(m.m01), m02(m.m02), m03(m.m03) { } template<typename scal_type> inline mat<scal_type, 2, 2>::mat(const scal_type a00, const scal_type a01, const scal_type a02, const scal_type a03) : m00(a00), m01(a01), m02(a02), m03(a03) { } template<typename scal_type> const mat<scal_type, 2, 2> mat<scal_type, 2, 2>::identity = mat<scal_type, 2, 2>(scal_type(1), scal_type(0), scal_type(1), scal_type(0)); typedef mat<float, 2, 2> mat2f; // trackball manipulator class class trackball_manipulator { public: trackball_manipulator() : _matrix(mat2f::identity) {} mat2f _matrix; }; // class trackball_manipulator trackball_manipulator _trackball_manip; int main(int argc, char **argv) { std::cout << _trackball_manip._matrix.data_array[0]; return (0); }Das Problem ist, dass nur im Debug-Modus die static const identity von mat2f nicht vor der Benutzung im Constructor des manipulators initialisiert ist. Im Release ist alles wie erwartet. Nehme ich den manipulator in die main rein geht auch alles super.
Fuer mein Verstaendnis ist da ein Compilerbug, oder was meint ihr?
Getestet mit Visual Studio 2005 (SP1) und 2008 (x86 und x64). Wie gesagt nur im Debug geht das vor den Baum.
-chris
-
Packe die Definition Deiner trackball_manipulator-Instanz mit in die Main-Funktion rein.
Du hast etwas zusammengeschraubt, dass die sogenannte "statische Initialisierungsreihenfolgenkatastrophe"
provoziert. Du hast quasi zwei globale Variablen, wobei Variable B von Variable A abhängt. Ob nun A oder B zuerst initialisiert wird, bleibt aber erstmal dem Compiler überlassen. So kannst Du also nie wissen, ob A schon initialisiert ist, wenn es für B benötigt wird.
Das kannst Du entweder dadurch lösen, dass Du Deine trackball_manipulator-Instanz, wie bereits gesagt, nicht global anlegst, oder Du benutzt das sogenannte "construct on first use"-Idiom. Google hilft Dir da weiter.int main(int argc, char **argv) { trackball_manipulator _trackball_manip; std::cout << _trackball_manip._matrix.data_array[0]; return (0); }PS: Such vielleicht auch nochmal nach "static initialization order fiasco". Das ist vielleicht etwas passender als "statische Initialisierungsreihenfolgenkatastrophe" :p
-
Die dynamische Initialisierung von Namensraumobjekten, die in der gleichen Übersetzungseinheit definiert werden, findet in der Reihenfolge ihrer Definition statt. Auf den ersten Blick mag das so erscheinen, als ob der Compiler hier also einen Fehler macht, denn wir haben
template<typename scal_type> const mat<scal_type, 2, 2> mat<scal_type, 2, 2>::identity = ... // (1) trackball_manipulator _trackball_manip; // (2)Allerdings ist (1) keine Objektdefinition, sondern eine Templatedefinition - obige Regel ist folglich darauf nicht (unmittelbar) anwendbar.
mat<scal_type,2,2>::identity wird implizit instantiiert im Defaultkonstruktor von trackball_manipulator. Leider schweigt sich der Standard meines Wissens darüber aus, was das bzgl. des Initialisierungszeitpunkts von mat2f::identity (relativ zu anderen Namensraumobjekten) bedeutet. Unabhängig davon darf der Compiler die dynamische Initialisierung von identity als statische Initialisierung durchführen, weil das Ergebnis nicht von der dynamischen Initialisierung anderer Namensraumobjekte abhängt und die Initialisierung auch keine Seiteneffekte hat, die andere Objekte beeinflusst. Damit erklärt sich das Verhalten im release-Build.
Eine explizite Spezialisierung für mat2f müsste dagegen in jedem Falle die erwartete Initialisierungsreihenfolge zeigen.
-
Danke für die ganzen Hinweise! Das macht mir das Problem jetzt sehr klar.
Ich werde es über eine statische Methode lösen, die eine Referenz auf eine statische Variable (ähnlich Mayers Singleton) zurückgibt. Hier ist die Reihenfolge der Initialisierung wenigstens klar definiert.
-chris