class A
{
public:
virtual std::string name() = 0;
};
class C
{
public:
C(A* p)
{
std::cout << p->name() << std::endl;
}
};
class B: public A
{
public:
B(): A(), m_obj(this)
{
}
virtual std::string name()
{
return "Hello, world!";
}
C m_obj;
};
Кратко: одно из полей класса в конструкторе по указателю на предка сделало вызов виртуальной функции.
Вопрос: будет работать правильно или все-таки undefined behavior? Т.е. гарантируется ли, что первым делом конструктор инициализирует vtbl?
При инициализации полей класса (в случае без наследования) можно легально использовать методы класса (только если они не используют еще непроинициализированные поля). Логично предположить, что в этом случае тоже все сработает. Думается, что порядок инициализации следующий: 1. Выделить память под базовый и производный классы. 2. Проинициализировать виртуальные таблицы. 3. Вызывать конструктор базового класса. 4. Вызвать конструктор производного класса.
Еще в твоем примере меня пугает, что ты нарушаешь константность класса при конструировании. Кажется, что передавать this куда-либо в конструкторе (тем более не по константному указателю) -- это порочная практика.
Ой, вот тут говорят, что я не прав :)
http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.1
А Вы уверены, что vtbl инициализируется в конструкторе? ;)
Vtbl инициализируется на этапе компиляции,vptr-указатель на vtbl для каждого отдельного объекта-на этапе выполнения. Речь наверное о нём:)
Про Undefined behavior,имхо, говорить не имеет смысла. Vtbl и Vptr не определены стандартом. Механизм виртуальных функций может реализовываться и по другому, но, согласно стандарту, intepretation of the call of a virtual function (всегда?) depends on the type of the object, что должно значить, что все должно работать правильно.
Конструтор, кажется, работает так:
1)Работа конструктора виртуальных базовых классов
2)Работа конструатора невиртуальных базовых классов
3)Инициализация vptr
4)Работа конструкторов по умолчанию объектов-полей класса не в списке инициализации.
5)Работа конструкторов объектов в списке инициализации (vptr уже проинициализирован.)
6)Тело конструктора.
Порядок 4<>5-не уверен.
Порядок 4-5 — объекты инициализируютя в порядке в котором они объявлены в классе, порядок в списке инициализации (принадлежность ему вообще) никакой роли не играет. Компиляторы ворнинги показывают если порядки отличаюся, проясняя этот момент для тех кто в танке.
По идее, поведение в таком случае регламентируется обычными правилами вызова виртуальных функций-членов из конструктора/деструктора. Цитата из стандарта:
12.7/4 Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2). When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class’s non-static data members, and the object to which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor’s or destructor’s class and not one overriding it in a more-derived class
То есть, грубо говоря, при прямом или косвенном вызове виртуальной функции конструируемого объекта вызовется переопределенный метод того класса, чей конструктор сейчас выполняется. (Это поведение кстати отличается от поведения в Java, например). Поэтому в приведенном выше коде все хорошо, и будет вызываться B::name().
Спасибо большое, я там проглядел слово indirectly.
Осталось надеяться, что стандарт соблюдают.
В таких относительно простых вещах все современные компиляторы стандарт соблюдают