ctor Reihenfolge der Auflösung



  • Hallo Gemeinde,

    Der Titel ist vieleicht nicht ganz korrekt, aber ich habe eine Frage zu den Konstruktoren. Zuerst mal der Code als Ilustration worum es geht

    template <class T>
      class Matrix<T, 2> : MatrixBase<T>
      {
        const int ib, ie, ni;
        const int jb, je, nj;
      public:
        Matrix(int n1, int n2) : MatrixBase<T>(n1*n2), ie(n1), je(n2), ib(1), jb(1), ni(ie - ib + 1), nj(je - jb + 1) { }
        Matrix(int n1, int n2, T* p) : MatrixBase<T>(n1*n2, p), ie(n1), je(n2), ib(1), jb(1), ni(ie - ib + 1), nj(je - jb + 1) { }
        Matrix(int _ib, int _ie, int _jb, int _je) : MatrixBase<T>(ni*nj), ie(_ie), je(_je), ib(_ib), jb(_jb), ni(ie - ib + 1), nj(je - jb + 1) { }
    //    Matrix(int _ib, int _ie, int _jb, int _je) : MatrixBase<T>((_ie - _ib + 1)*(_je - _jb + 1)), ie(_ie), je(_je), ib(_ib), jb(_jb), ni(ie - ib + 1), nj(je - jb + 1) { }
        Matrix(int _ib, int _ie, int _jb, int _je, T* p) : MatrixBase<T>(ni*nj, p), ie(_ie), je(_je), ib(_ib), jb(_jb), ni(ie - ib + 1), nj(je - jb + 1) { }
    //    Matrix(int _ib, int _ie, int _jb, int _je, T* p) : MatrixBase<T>((_ie - _ib + 1)*(_je - _jb + 1), p), ie(_ie), je(_je), ib(_ib), jb(_jb), ni(ie - ib + 1), nj(je - jb + 1) { }
    
        T& operator() (int i, int j)
        {
          _ASSERT(i >= ib && i <= ie);
          _ASSERT(j >= jb && j <= je);
          return elem[(i - ib) + ni * (j - jb)];
        }
        Matrix<T, 1> operator() (int j)
        {
          _ASSERT(j >= jb && j <= je);
          return Matrix<T, 1>(ib, ie, &elem[ni * (j - jb)]);
        }
      };
    

    Der Code funktioniert so unter VS2013 aber ist das auch sicher oder hatte ich gerade Glück.
    Es geht um die Konstruktoren bei denen jeweils eine Variante auskommentiert ist. Darf ich hier ni und nj als Argument für die Basisklasse verwenden?

    Gruß Andreas Georg


  • Mod

    Zuerst werden alle Basisklassen initialisiert, von links nach rechts wie sie in der Angabe der Basissklassen stehen (bei Mehrfachvererbung). Dann alle Member, von oben nach unten, wie sie in der Klassendefinition stehen.
    Die Reihenfolge in der Initialisierungsliste ist ausdrücklich egal, um dies nochmal zu betonen.

    Virtuelle Basisklassen und ähnliches spar ich mir mal, da man es bei Bedarf nachschlagen kann und obige Regeln in 99% aller Fälle reichen (müsste ich außerdem selber nachgucken :p ).

    Destruktorreihenfolge ist exakt umgekehrt.

    Zu deiner Frage: Das geht so folglich nicht, da ni und nj noch nicht initialisiert sind, wenn der Basisklassenkonstruktor aufgerufen wird: Dieser erhält dann uninitalisierten Müll. Es wundert mich daher sehr, wenn das angeblich funktioniert, denn das wäre schon ein sehr großer Zufall. edit: Ist das möglicherweise kein C++, sondern C++/CLI? Da ist das nämlich anders. Das ist aber auch (trotz des Namens und obwohl es von Microsoft gerne als C++ verkauft wird) eine andere Sprache.



  • Danke, die Antwort hatte ich erwartet.
    Währe halt schön gewesen den Ausdruck nicht 2 mal schreiben zu müssen. Aber so bin ich auf der sicheren Seite.

    SeppJ schrieb:

    Es wundert mich daher sehr, wenn das angeblich funktioniert, denn das wäre schon ein sehr großer Zufall. edit: Ist das möglicherweise kein C++, sondern C++/CLI? Da ist das nämlich anders. Das ist aber auch (trotz des Namens und obwohl es von Microsoft gerne als C++ verkauft wird) eine andere Sprache.

    Nein das ist kein CLI, das ist ganz normal Win32 Konsole.
    Getestet hatte ich mit

    template <class T>
      class MatrixBase
      {
      protected:
        T*           elem;
        int          sz;
        mutable bool owns;
      public:
        MatrixBase(int n) :elem(new T[n]()), sz(n), owns(true)
        {
          std::cerr << "new[" << n << "]->" << elem << "\n";
        }
    
        MatrixBase(int n, T* p) :elem(p), sz(n), owns(false)
        {
        }
    
        ~MatrixBase()
        {
          if (owns) {
            // std::cerr << "delete[" << sz << "] " << elem << "\n";
            delete[]elem;
          }
        }
    
        // if necessay, we can get to the raw matrix:
        T*       data()       { return elem; }
        const T* data() const { return elem; }
        int      size() const { return sz; }
      };
    

    Da kam mehrfach das raus was ich will.

    Nutzt aber nichts, wenn es nicht garantiert ist.

    Gruß Andreas Georg


  • Mod

    müsste ich außerdem selber nachgucken :p

    12.6.2/11 schrieb:

    In a non-delegating constructor, initialization proceeds in the following order:
    - First, and only for the constructor of the most derived class (1.8), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.

    Nun, hier ist eigentlich nur interessant, dass Member von oben nach unten initialisiert werden, so erzeugt der Code im OP undefiniertes Verhalten.

    Ein paar Kritiken an deiner MatrixBase:

    • Variablen macht man praktisch nie protected . Mach' sie private , und biete für owns noch einen Getter.
    • Speichere die Größe in std::size_t , nicht int .
    • Was ist mit einem Kopierkonstruktor? Und einem Zuweisungsoperator? Die werden implizit definiert! Und da du VS2013 verwendest, könntest du dir auch Gedanken über Move-Semantik machen.

Anmelden zum Antworten