Вызов чистой виртуальной функции

Возможное дублирование:
Вызов виртуальных функций внутри конструкторов

Посмотри на этот код. В конструкторе базового класса мы можем вызвать чистую виртуальную функцию, используя указатель this. Теперь, когда я хочу создать типизированный указатель на тот же класс и привести this к тому же типу. Он выдает исключение времени выполнения «исключение вызова чистой виртуальной функции». Почему это так?

#include <iostream>

using namespace std;

class Base
{
  private:
  virtual void foo() = 0;
  public:
  Base()
  {
    //Uncomment below 2 lines and it doesn't work (run time exception)
    //Base * bptr = (Base*)this;
    //bptr->foo();
    //This call works
    this->foo();
  }
};

void
Base::foo()
{
  cout << "Base::foo()=0" << endl;
}

class Der : public Base
{
  public:
  Der()
  {
  }
  public:
  void foo()
  {
    cout << "Der::foo()" << endl;
  }
};

int main()
{
  cout << "Hello World!" << endl;
  Der d;
}

person siddhusingh    schedule 02.03.2012    source источник
comment
Этот вопрос не повторяется. Здесь конкретно возникает вопрос, почему this->foo() работает, тогда как вызов через приведение не выполняется.   -  person Agnel Kurian    schedule 06.10.2012


Ответы (2)


Вы должны никогда не вызывать виртуальные функции в конструкторе.

Виртуальные функции не отправляются так, как вы думаете. Скорее, во время построения динамический тип базового подобъекта, который создается, является базовым типом, и, таким образом, функция отправляется базовой функции (которая в вашем случае является чисто виртуальной).

Просто не делай этого.

(Причина очевидна: при построении производного объекта базовый подобъект обязательно должен быть построен первым, поэтому внешний производный объект даже не существует во время базового построения.)


Изменить: вот еще одно объяснение. Компиляторам вполне разрешено и рекомендуется выполнять виртуальную отправку статически, если они могут это делать. В этом случае уже во время компиляции определяется, какая фактическая функция будет вызвана. Это происходит, когда вы говорите foo() или this->foo() в конструкторе Base или когда вы говорите x.Base::foo() в каком-либо другом контексте, где Derived x; - ваш объект. Когда отправка происходит статически, либо реализация Base::foo() вызывается напрямую, либо вы получаете ошибку компоновщика, если реализации нет.

С другой стороны, если отправка происходит динамически, т. Е. Во время выполнения, то существует вероятность, хотя и необычная, что отправка фактически завершит выбор Base::foo() в качестве конечной цели. Это не может произойти в «нормальных» условиях, потому что компилятор не позволит вам создать экземпляр класса с чисто виртуальными функциями, и поэтому целью обычной динамической отправки всегда является функция, для которой должна существовать реализация (или, по крайней мере, вы получил бы ошибку компоновщика, если бы вы его не связали).

Но есть еще одна ситуация, о которой идет речь: компилятор решает выполнить отправку во время выполнения по какой-либо причине, и дипатч заканчивается чисто виртуальной функцией. В этом случае ваша программа завершается. Не имеет значения, реализована функция или нет, просто она не имеет записи в иерархии полиморфных классов (воспринимайте это как «нулевой указатель в vtable», отсюда = 0). Чтобы это произошло, динамический тип объекта должен быть типом абстрактного базового класса, и отправка должна происходить динамически. Первое может быть достигнуто только внутри базового конструктора производного объекта, а второе требует, чтобы вы убедили компилятор не отправлять вызов статически. Вот где проявляется разница между this->foo() (статический) и Base * p = this; p->foo(); (динамический). (Также сравните это с x.Base::foo(), который отправляется статически.)

Все это просто следствие реализации и, конечно же, покрывается общим «неопределенным поведением». Если вы хотите убрать что-то из этого, то это то, что динамическая диспетчеризация не может найти чисто виртуальную функцию. И, конечно же, вы никогда не должны вызывать виртуальные функции в конструкторе.

person Kerrek SB    schedule 02.03.2012
comment
Вы можете добавить ссылку на artima.com/cppsource/ Nevercall.html - person Joachim Isaksson; 02.03.2012
comment
Совершенно нормально вызывать виртуальные функции в конструкторе, если известно, как они работают. Так что никогда немного суров. В этом случае вызываемая функция является чистой виртуальной функцией, а вызов чистой виртуальной функции в конструкторе - это UB. - person Alok Save; 02.03.2012
comment
Вы можете вызывать виртуальную функцию, если это не чистая виртуальная функция, и пока вы знаете, что то, что вы вызываете, является методом самого класса и не ожидает какого-либо полиморфизма. Вызов чистой виртуальной функции из конструктора является неопределенным поведением, даже если у него есть реализация. - person CashCow; 02.03.2012
comment
Я согласен с тем, что мы не должны его использовать. Тем не менее мне любопытно узнать, почему это вызывает чистую виртуальную функцию, а указатель на тот же класс - нет? Какой фактор используется компилятором для его решения? Однако это не ошибка времени компиляции. - person siddhusingh; 03.03.2012
comment
@siddhusingh: Вот что происходит: когда вы создаете производный класс D, который наследуется от B, то первое, что происходит, - конструируется B-подобъект, который заканчивается запуском конструктора B. В то время тип this внутри конструктора B - B*. Затем конструируются все члены D, и только после этого выполняется тело конструктора D, и только в это время типом this является D*. Если вы вызываете не-чистый виртуальный объект внутри B-конструктора, вы просто отправляетесь в реализацию B. - person Kerrek SB; 03.03.2012
comment
@KerrekSB: Я полностью согласен с вашей точкой зрения. На мой вопрос пока нет ответа. Когда я вызываю чистую виртуальную функцию (определенную только в B) в конструкторе класса B, используя указатель this, она работает, но когда я привожу тип this к типу указателя ЖЕСТКОГО класса (т.е. только B) в конструкторе класса B , то он выдает исключение во время выполнения, и это меня смущает. В том же месте вызов с использованием this работает, тогда как вызов с использованием B * не выполняется. Прошу прощения, если вы уже ответили на это. Но пока не могу разобраться. Это вызывает исключение как в g ++ 4.4, так и в Visual Studio. - person siddhusingh; 03.03.2012
comment
@siddhusingh: это неопределенное поведение. Все может случиться. Вы предоставляете определение чистого виртуального, поэтому в некоторых случаях вы видите, что он вызывается, а в других случаях компилятор решает провести оптимизацию, которую ему разрешено делать, и приводит к сбою вашей программы. Такова природа неопределенного поведения - любое поведение "правильное"! - person Kerrek SB; 03.03.2012
comment
@siddhusingh: Я добавил некоторые объяснения того, что, вероятно, происходит! - person Kerrek SB; 03.03.2012
comment
Отлично @KerrekSB. Это то, что я искал. Спасибо - person siddhusingh; 04.03.2012

Это неправильная процедура для того, что вы, вероятно, пытаетесь сделать.

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

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

Вызов чистой виртуальной функции из конструктора является неопределенным поведением. Это будет иметь место всякий раз, когда ваш класс создается, независимо от того, вызывает ли он его непосредственно из конструктора (который компилятор может перехватить и предупреждать) или из метода, который вызывает ваш конструктор (который потенциально выходит за рамки компилятор или компоновщик для обнаружения).

person CashCow    schedule 02.03.2012