Правильно ли это: виртуальный метод производного вызывается до построения базового объекта?
Я знаю, что внутри конструктора базового класса - при вызове виртуального метода - вызывается базовый метод, а не производный - см. вызов виртуальных функций внутри конструкторов.
Мой вопрос относится к этой теме. Мне просто интересно, что произойдет, если я вызову виртуальный метод в конструкторе производного класса-но до построения базовой части. Я имею в виду вызов виртуального метода для вычисления аргумента конструктора базового класса, см. код:
class Base {
public:
Base(const char* name) : name(name) {
cout << "Base():" << name << endl;
}
virtual const char* getName() {
cout << "Base::getName()" << endl;
return "Base";
}
protected:
const char* name;
};
class Derived : public Base {
public:
Derived() : Base(getName()) {
cout << "Derived():" << name << endl;
}
virtual const char* getName() {
cout << "Derived::getName()" << endl;
return "Derived";
}
};
int main() {
Derived d;
}
Компилятор g++ (4.3.X-версии 4.5 x) вывод:
Derived::getName()
Base():Derived
Derived():Derived
Однако я ожидал бы:
Base::getName()
Base():Base
Derived():Base
Это не выглядит неправильным - но рассмотрим этот пример, который производит segmentation fault
:
class Derived : public Base {
public:
Derived() : Base(getName()), name(new string("Derived")) {
cout << "Derived():" << Base::name << endl;
}
virtual const char* getName() {
cout << "Derived::getName()" << endl;
return name->c_str();
}
private:
string* name;
};
Пожалуйста, ответьте: это правильное поведение g++? Что об этом говорит стандарт C++? Может быть, это неопределенное поведение?
[UPDATE1] Я принимаю во внимание ответы Роберта и Оли - и я изменил свой первый пример. Тогда getName() называется "виртуальным" - и это приводит к ошибке сегментации. Пожалуйста, ответьте на мой вопрос в этой части тоже.
const char* virtualGetName(Base* basePtr)
{
return basePtr->getName();
}
class Derived : public Base {
public:
Derived() : Base(virtualGetName(this)) {
cout << "Derived():" << Base::name << endl;
}
virtual const char* getName() {
cout << "Derived::getName()" << endl;
return "Derived";
}
};
2 ответа:
Все ваши примеры демонстрируют неопределенное поведение. Стандартные состояния языка C++ (C++11 §12.6.2/13):
Функции-члены (включая виртуальные функции-члены) могут быть вызваны для строящегося объекта. Аналогично, строящийся объект может быть операндом оператора
typeid
или оператораdynamic_cast
.Однако, если эти операции выполняются в ctor-инициализаторе (или в функции, вызываемой прямо или косвенно из ctor-инициализатор ) до завершения всех mem-инициализаторов для базовых классов результат операции не определен.
Вызывается функция-член
Таким образом, поведение не определено.getName()
из списка инициализации ( ctor-инициализатор) конструктора классаDerived
. Этот вызов функции-члена должен выполняться до завершения инициализатора дляBase
(mem-инициализатора ДляBase
), поскольку он является аргументом для инициализатора сам.Как правило, никогда не вызывают виртуальные функции во время построения или разрушения.
Может показаться, что вы делаете это, но это не так.Мне просто интересно, что произойдет, если я вызову виртуальный метод в конструкторе производного класса-но до построения базовой части.
В первом примере
Derived::getName()
не зависит отthis
, поэтому вызов метода работает. Во втором примереDerived::getName()
действительно зависит отthis
. Посколькуthis->name
еще не задан, он указывает на неопределенное местоположение и дает вам segfault.
this->name
еще не установлен, потому что первое, что делает конструкторDerived
, - это вызывает конструкторBase
. Если вы задаете параметры для передачи конструкторуBase
, он сначала обрабатывает их. Затем он создает экземпляры переменных-членов класса, вызывая их конструкторы. Список инициализаторов можно использовать для передачи параметров этим конструкторам, но он не может изменить порядок их вызова. На этом шаге инициализируетсяname
. Наконец, выполняется тело конструктораDerived
.