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

Когда я вызываю виртуальную функцию из базового конструктора, компилятор не выдает ошибок. Но когда я вызываю чисто виртуальную функцию из конструктора базового класса, возникает ошибка компиляции.

Рассмотрим пример программы ниже:

#include <iostream>

using namespace std;
class base
{
   public:
      void virtual virtualfunc() = 0;
      //void virtual virtualfunc();
      base()
      {
         virtualfunc();
      }
};

void base::virtualfunc()
{
   cout << " pvf in base class\n";
}

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

int main()
{
   derived d;
   base *bptr = &d;
   bptr->virtualfunc();

   return 0;
}

Здесь видно, что чистая виртуальная функция имеет определение. Я ожидал, что чистая виртуальная функция, определенная в базовом классе, будет вызываться при выполнении bptr->virtualfunc(). Вместо этого выдает ошибку компиляции:

ошибка: абстрактная виртуальная `virtual void base :: virtualfunc () 'вызывается из конструктора

Что является причиной этого?


person nitin_cherian    schedule 27.12.2011    source источник


Ответы (4)


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

C ++ 03 10.4 / 6 состояний

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

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

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

person Alok Save    schedule 27.12.2011
comment
@Als: Я определил чисто виртуальную функцию. Все-таки почему ошибка компиляции? - person nitin_cherian; 27.12.2011
comment
@LinuxPenseur: Вы уверены, что скомпилировали правильный код? В gcc-4.3.4 Без определения есть ошибка, но С определением ошибки нет, но отсутствие ошибки не означает, что это правильно, это все еще неопределенное поведение. - person Alok Save; 27.12.2011
comment
@LinuxPenseur: это неопределенное поведение в соответствии со стандартом, и это качество реализации, чтобы сообщить вам до, что он потенциально может дать сбой во время выполнения. Если вы действительно хотите вызвать эту функцию из конструктора, отключите механизм виртуальной диспетчеризации: base::virtualfunc(), и это должно устранить ошибку (сделав явным в пользовательском коде то, что вы вызываете. Обратите внимание, что в некоторых компиляторах рассматриваемый код фактически в любом случае вызовите base::virtualfunc() (даже если он будет чистым виртуальным, если он был определен) без сбоев, но это просто другая версия UB. - person David Rodríguez - dribeas; 27.12.2011
comment
@Als: Вы получаете ошибку компиляции, потому что вы не определили чистую виртуальную функцию Как правило, вы получите ошибку link для функции, которая не определена. В этом случае компилятор применяет темную магию к тому, что означает UB. Для этого компилятора UB означает выполнение статической отправки, если функция известна, или сбой компиляции, если она неизвестна. Мне не очень нравится этот подход, поскольку если вы переместите определение в отдельный TU, у вас будут две точно эквивалентные программы, одна из которых компилируется и запускается, а другая не может даже скомпилироваться. - person David Rodríguez - dribeas; 27.12.2011
comment
@ DavidRodríguez-dribeas: Согласен, компилятор здесь творит черную магию. В общем, вы получите ошибку ссылки для функции, которая не определена верно в случае обычных или не чистых виртуальных функций, но согласно стандарту чистой виртуальной функции разрешено существовать без определения (который был Q за несколько минут до этого). Я пытался сказать, что это странное, но допустимое поведение, возможно, не с подходящими словами. Спасибо за разъяснение по этому поводу. Ваши ответы всегда краткие и по существу. - person Alok Save; 27.12.2011
comment
Что касается потенциальной ошибки связи в случае чистой виртуальной функции, следует учитывать две вещи. С одной стороны, все виртуальные функции, кроме чисто виртуальных, считаются odr-used независимо от того, использует ли программа это конкретное переопределение. Похоже, это указывает на то, что, поскольку чистая виртуальная функция не используется, ее не нужно определять. С другой стороны, чистая виртуальная функция может быть явно odr-used (учтите base::virtualfunc() в коде, и это вызовет ошибку компоновщика. - person David Rodríguez - dribeas; 28.12.2011
comment
... Проблема на этом этапе с этим компилятором заключается в том, что он решает вызвать чистый виртуальный метод (и использовать его) в зависимости от кода, который он ранее видел, что нарушает отдельную модель компиляции. Компилятор ведет себя по-разному в зависимости от кода, присутствующего в одном TU, что, мягко говоря, сбивает с толку. - person David Rodríguez - dribeas; 28.12.2011

В C ++ 11 есть обходной путь.

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

Вы можете / должны использовать ключевое слово final, чтобы гарантировать, что поведение подклассов не является непредсказуемым.

См .: Делегированный конструктор C ++ 11 Чистые виртуальные вызовы методов и функций - Опасности?

#include <string>

/**************************************/
class Base
{
public:
    int sum;
    virtual int Do() = 0;

    void Initialize()
    {
        Do();
    }
    Base()
    {
    }
};

/**************************************/
// Optionally declare class as "final" to avoid
// issues with further sub-derivations.
class Derived final : public Base
{
public:

    virtual int Do() override final
    {
        sum = 0 ? 1 : sum;
        return sum / 2 ; // .5 if not already set.
    }

    Derived(const std::string & test)
        : Derived() // Ensure "this" object is constructed.
    {
        Initialize(); // Call Pure Virtual Method.
    }
    Derived()
        : Base()
    {
        // Effectively Instantiating the Base Class.
        // Then Instantiating This.
        // The the target constructor completes.
    }
};




/********************************************************************/
int main(int args, char* argv[])
{
    Derived d;
    return 0;
}
person e.s. kohen    schedule 04.02.2013

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

http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.5

Когда вы пытаетесь вызвать чистую виртуальную функцию, ее реализации еще нет.

Есть много решений. Самый простой - создать другую функцию-член «init ()», которую вы будете вызывать после конструктора базового класса.

person CyberGuy    schedule 27.12.2011
comment
Да, есть способы обойти это. Двухэтапная инициализация - НЕ хороший выбор. Предпочитаю паттерн PIMP. - person Martin York; 27.12.2011

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

В любом случае вызывать ЛЮБУЮ виртуальную функцию из конструктора - действительно плохая идея, потому что это делает цель вашего кода неясной и запутанной.

person Gerald    schedule 27.12.2011