Страницы

Поиск по вопросам

понедельник, 9 декабря 2019 г.

Порядок вызова в списке инициализации конструктора

#cpp


class A {
public:
   A()
   {
      v.resize(a);
   }

   A(int a, int b): a(a), b(b), A() {} // !

private:
   std::vector v;
   int a, b;
};


Гарантирован ли тут порядок вызова a, b, A(), или может случится так, что сначала
вызовется A(), а потом a, b? Если нет, то как исправить, чтобы получить нужное поведение?
    


Ответы

Ответ 1



Порядок вызова соответствует порядку объявления членов, т.е. что вы ни напишете в конструкторе, сначала будет инициализирован v, затем a и b.

Ответ 2



Начнем с того, что данное объявление класса class A { public: A() { v.resize(a); } A(int a, int b): a(a), b(b), A() {} // ! private: std::vector v; int a, b; }; является некорректным. Если используется делегирующий конструктор, то есть конструктор, который делегирует инициализацию другому конструктору, как в данном объявлении A(int a, int b): a(a), b(b), A() {} // ! ^^^ то список инициализации должен содержать только вызов конструктора, которому делегируется инициализация. Из стандарта C++ (12.6.2 Initializing bases and members) 6 A mem-initializer-list can delegate to another constructor of the constructor’s class using any class-or-decltype that denotes the constructor’s class itself. If a mem-initializer-id designates the constructor’s class, it shall be the only mem-initializer; the constructor is a delegating constructor, and the constructor selected by the mem-initializer is the target constructor. The principal constructor is the first constructor invoked in the construction of an object (that is, not a target constructor for that object’s construction). The target constructor is selected by overload resolution. Once the target constructor returns, the body of the delegating constructor is executed. If a constructor delegates to itself directly or indirectly, the program is ill-formed; no diagnostic is required Правильно было бы ваш класс определить, например, следующим образом class A { public: A() : A( 0, 0 ) {} A( int a, int b ): a( a ), b (b ) { v.resize( a ); } private: std::vector v; int a, b; }; То есть делегирующим конструктором является конструктор по умолчанию. Что касается порядка инициализации, то он описан в следующей цитате из стандарта C++ (12.6.2 Initializing bases and members) 13 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. — Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers). — Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers). — Finally, the compound-statement of the constructor body is executed. То есть сначала вызываются конструкторы виртуальных базовых классов. Затем вызываются конструкторы непосредственно базовых классов в том порядке, как они перечислены при объявлении производного класса. Затем инициализируются члены производного класса в порядке их объявления, и, наконец, выполняется тело самого конструктора производного класса. Обратите внимание на то, что каждое выражение инициализации является так называемым полным выражением. Это означает, что побочные эффекты применяются после вычисления выражений инициализации и видны при следующей инициализации. Из стандарта C++ (12.6.2 Initializing bases and members) ... The initialization performed by each mem-initializer constitutes a full-expression. Any expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization. A mem-initializer where the mem-initializer-id denotes a virtual base class is ignored during execution of a constructor of any class that is not the most derived class. Рассмотрите следующий пример. #include struct A { A(int i) : a(i) {} int a; }; struct B : virtual A { B(int i) : A(i++), b(i++) {} int b; }; struct C : virtual A { C(int i) : A(i++), c( i++ ) {} int c; }; struct D : B, C { D(int i = 0) : d(i++), C(i++), B(i++), A(i++) {} int d; }; int main() { D d; std::cout << "a = " << d.a << ", b = " << d.b << ", c = " << d.c << ", d = " << d.d << std::endl; } В конструкторе класса D элементы инициализации перечислены в произвольном порядке. Как будет выполняться инициализация? Согласно приведенной цитате из стандарта сначала вызывается конструктор виртуального класса A. Поэтому переменная a получит значение 0. Затем вызывается конструктор непосредственного базового класса, который указан первым в списке объявления производного класса D. Этим классом является класс B. В списке инициализации конструктора этого класса присутствует вызов его собственного базового класса A. Однако такой вызов игнорируется. Конструктор класса A уже был вызван из класса D. Поэтому единственное, что делает конструктор класса B, - это инициализирует переменную b, которая получит значение 1. Затем вызывается конструктор класса C. Его собственный конструктор также вызывает конструктор класса A. Но этот вызов будет проигнорирован по той же самой причине, что и для класса B. Переменная c получит значение 2. И, наконец, произойдет инициализации переменной d, которая объявлена в классе D. Вывод программы на консоль подтверждает справедливость сказанного. a = 0, b = 1, c = 2, d = 3

Ответ 3



Вы же можете переписать ваш класс, чтобы определить другой порядок инициализации: class A { ... private: int a, b; std::vector v; }; Тогда конструктор можно будет определить следующим образом: A() : a(0), b(0), v(a) { } Совет: лучше для переменной, хранящей (принимающей) размер использовать тип size_t. Решение с использованием делегирующего конструктора: class A { public: A(int a, int b) : a(a), b(b) { v.resize(a); } A() : A(0, 0) { } private: int a, b; std::vector v; }; Решение с выделением общего кода инициализации в отдельную функцию init(): class A { public: A() : a(0), b(0) { init(); } A(int a, int b) : a(a), b(b) { init(); } private: void init() { v.resize(a); } private: int a, b; std::vector v; };

Комментариев нет:

Отправить комментарий