Правильно ли это: виртуальный метод производного вызывается до построения базового объекта?


Я знаю, что внутри конструктора базового класса - при вызове виртуального метода - вызывается базовый метод, а не производный - см. вызов виртуальных функций внутри конструкторов.

Мой вопрос относится к этой теме. Мне просто интересно, что произойдет, если я вызову виртуальный метод в конструкторе производного класса-но до построения базовой части. Я имею в виду вызов виртуального метода для вычисления аргумента конструктора базового класса, см. код:

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 4

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.