Чистый вызов виртуальной функции из базового ктора - PullRequest
5 голосов
/ 09 февраля 2012

Рассмотрим следующий пример кода:

#include <iostream>

using namespace std;

class base
{
   public:
      base()
      {
         bar(); //Line1
         this->bar(); //Line2
         base *bptr = this; 
         bptr->bar(); //Line3
         ((base*)(this))->bar(); //Line4
      }

      virtual void bar() = 0;
};

class derived: base
{
   public:
      void bar()
      {
         cout << "vfunc in derived class\n";
      }
};

int main()
{
   derived d;
}

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

Теперь сфокусируйтесь на Line1, Line2, Line3 и Line4.

Я понимаю : Line1 дает ошибку компиляции, потому что чисто виртуальная функция не может быть вызвана из ctor.

Вопросы:

  1. Почему Line2 и Line4 не дают compilation error по той же причине, которая указана в I understand заявлении выше ?. Звонки в Line2 и Line4 в конечном итоге вызовут только linker-error.

  2. Почему Line3 не дает ни ошибки компиляции, ни ошибки компоновщика, а только run-time exception?

Реальный пример UB при вызове виртуальной функции Pure через конструктор:

Real-Life example of UB when Pure virtual function call through constructor

Ответы [ 6 ]

6 голосов
/ 09 февраля 2012

Во всех четырех случаях поведение не определено;поэтому именно то, что происходит, зависит от того, что делает ваш компилятор перед лицом неверного ввода.

Компилятор может попытаться диагностировать проблему, чтобы выдать предупреждение;это легко сделать для строки 1 и труднее для других строк, что объясняет, почему вы видите только предупреждение для строки 1.

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

В строке 3 компилятор, должно быть, решил, что слишком сложно определить, может ли он генерировать статический вызов, поэтому он сгенерировал динамическийпозвони вместо.Отслеживать значение переменной довольно сложно, чем понять, что временный указатель должен ссылаться на this, и часто вообще не представляется возможным.Вот почему вы получаете ошибку во время выполнения.

Конечно, все это неопределенное поведение, которое может меняться от компилятора к компилятору или в соответствии с фазой луны.

Если бы у функции была реализация, то было бы правильно назвать ее статически , как Base::bar() или bptr->Base::bar().Динамический вызов будет по-прежнему давать неопределенное поведение.

5 голосов
/ 09 февраля 2012

Вызов виртуальной функции Pure из конструктора является неопределенным поведением , и компилятор может показать любое поведение.

Ссылка:
C ++ 03 Стандарт 10.4 / 6:

"Функции-члены могут вызываться из конструктора (или деструктора)абстрактный класс; эффект от виртуального вызова (10.3) чисто виртуальной функции прямо или косвенно для объекта, создаваемого (или уничтожаемого) из такого конструктора (или деструктора), не определен. "

Стандарт C ++ определяет неопределенное поведение в:

[defns.undefined] 1.3.12 неопределенное поведение

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

1 голос
/ 09 февраля 2012

Какой компилятор вы используете?

Vc10 и gcc 4.6 все компилируются нормально. gcc выдает приятное предупреждение о вызове виртуальной функции из конструктора, просто используя функцию base :: bar () вместо полиморфной.

Вероятно, bocs вызывает виртуальный конструктор из Undefined Behavior.

1 голос
/ 09 февраля 2012

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

0 голосов
/ 09 февраля 2012

Ваш код содержит неопределенное поведение, поэтому все, что делает компилятор, является правильным.Все вызовы bar() в вашем коде требуют динамического разрешения и приведут к вызову чисто виртуальной функции;это неопределенное поведение.(Вы можете вызвать Base::bar(), если напишите так, и функция существует, поскольку динамическое разрешение не требуется.) Тот факт, что код компилируется, не означает, что он будет работать успешно;например, в случае с g ++ я вполне уверен, что он вылетит с сообщением об ошибке.

Жалуется компилятор или нет, вероятно, зависит от того, сколько усилий потребуется для разрешения динамического разрешения привремя компиляции.Без оптимизации он почти наверняка не сможет разрешить 3 во время компиляции, но я несколько удивлен, что он по-разному обрабатывает 1 и 2.

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

0 голосов
/ 09 февраля 2012

Странный факт C ++: допустимо (и очень редко полезно) определять чисто виртуальную функцию.

Вы можете попробовать добавить

void base::bar() { cout << "Wuh?"; }

и вы найдете строку 2 и строку 4, вызывающую эту функцию.

Легальный способ назвать это определение - сделать это явно: base::bar();

Компилятору удалось оптимизировать виртуальный вызов (который потерпит неудачу) для не виртуального вызова в этих случаях. Это не требуется и не мешает этому; все ваши звонки имеют неопределенное поведение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...