Подтвердить что ты не робот

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

У меня есть базовый класс MyBase, который содержит чистую виртуальную функцию:

void PrintStartMessage() = 0

Я хочу, чтобы каждый производный класс вызывал его в своем конструкторе

то я поместил его в конструктор базового класса (MyBase)

 class MyBase
 {
 public:

      virtual void PrintStartMessage() =0;
      MyBase()
      {
           PrintStartMessage();
      }

 };

 class Derived:public MyBase
 {     

 public:
      void  PrintStartMessage(){

      }
 };

void main()
 {
      Derived derived;
 }

но я получаю ошибку компоновщика.

 this is error message : 

 1>------ Build started: Project: s1, Configuration: Debug Win32 ------
 1>Compiling...
 1>s1.cpp
 1>Linking...
 1>s1.obj : error LNK2019: unresolved external symbol "public: virtual void __thiscall MyBase::PrintStartMessage(void)" ([email protected]@@UAEXXZ) referenced in function "public: __thiscall MyBase::MyBase(void)" ([email protected]@[email protected])
 1>C:\Users\Shmuelian\Documents\Visual Studio 2008\Projects\s1\Debug\s1.exe : fatal error LNK1120: 1 unresolved externals
 1>s1 - 2 error(s), 0 warning(s)

Я хочу заставить все производные классы...

A- implement it

B- call it in their constructor 

Как я должен это делать?

4b9b3361

Ответ 1

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

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

Ответ 2

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

Самое близкое к выполнению чего-либо подобного - сначала полностью построить свой объект, а затем вызвать метод после:

template <typename T>
T construct_and_print()
{
  T obj;
  obj.PrintStartMessage();

  return obj;
}

int main()
{
    Derived derived = construct_and_print<Derived>();
}

Ответ 3

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

Вызов PrintStartMessage после создания MyBase

Предположим, что вы хотите сделать что-то вроде этого:

class MyBase {
public:
    virtual void PrintStartMessage() = 0;
    MyBase() {
        printf("Doing MyBase initialization...\n");
        PrintStartMessage(); // ⚠ UB: pure virtual function call ⚠
    }
};

class Derived : public MyBase {
public:
    virtual void PrintStartMessage() { printf("Starting Derived!\n"); }
};

То есть желаемый результат:

Doing MyBase initialization...
Starting Derived!

Но это именно то, для чего нужны конструкторы! Просто удалите виртуальную функцию и заставьте конструктор Derived сделать работу:

class MyBase {
public:
    MyBase() { printf("Doing MyBase initialization...\n"); }
};

class Derived : public MyBase {
public:
    Derived() { printf("Starting Derived!\n"); }
};

Результат - это то, что мы ожидаем:

Doing MyBase initialization...
Starting Derived!

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

Вызов PrintStartMessage до создания MyBase

Как сказано выше, если вы хотите вызвать PrintStartMessage до того, как Derived был создан, вы не можете сделать это, потому что нет еще объекта Derived для PrintStartMessage который будет вызываться. Не имеет смысла требовать, чтобы PrintStartMessage был нестатическим элементом, поскольку он не имел бы доступа ни к одному из элементов Derived данных.

Статическая функция с заводской функцией

В качестве альтернативы мы можем сделать его статическим членом, например, так:

class MyBase {
public:
    MyBase() {
        printf("Doing MyBase initialization...\n");
    }
};

class Derived : public MyBase {
public:
    static void PrintStartMessage() { printf("Derived specific message.\n"); }
};

Возникает естественный вопрос, как это будет называться?

Есть два решения, которые я вижу: одно похоже на @greatwolf, где вы должны вызвать его вручную. Но теперь, поскольку это статический член, вы можете вызвать его до того, как будет создан экземпляр MyBase:

template<class T>
T print_and_construct() {
    T::PrintStartMessage();
    return T();
}

int main() {
    Derived derived = print_and_construct<Derived>();
}

Выход будет

Derived specific message.
Doing MyBase initialization...

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

Второе решение состоит в том, чтобы прибегнуть к шаблону CURLY CURRURING Template (CRTP). MyBase полный тип объекта во время компиляции, он может выполнить вызов из конструктора:

template<class T>
class MyBase {
public:
    MyBase() {
        T::PrintStartMessage();
        printf("Doing MyBase initialization...\n");
    }
};

class Derived : public MyBase<Derived> {
public:
    static void PrintStartMessage() { printf("Derived specific message.\n"); }
};

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

Доступ к MyBase из PrintStartMessage с помощью CRTP

Пока MyBase выполняется, он уже в порядке, чтобы получить доступ к своим членам. Мы можем сделать PrintStartMessage быть в состоянии получить доступ к MyBase, который назвал его:

template<class T>
class MyBase {
public:
    MyBase() {
        T::PrintStartMessage(this);
        printf("Doing MyBase initialization...\n");
    }
};

class Derived : public MyBase<Derived> {
public:
    static void PrintStartMessage(MyBase<Derived> *p) {
        // We can access p here
        printf("Derived specific message.\n");
    }
};

Следующее также допустимо и очень часто используется, хотя и немного опасно:

template<class T>
class MyBase {
public:
    MyBase() {
        static_cast<T*>(this)->PrintStartMessage();
        printf("Doing MyBase initialization...\n");
    }
};

class Derived : public MyBase<Derived> {
public:
    void PrintStartMessage() {
        // We can access *this member functions here, but only those from MyBase
        // or those of Derived who follow this same restriction. I.e. no
        // Derived data members access as they have not yet been constructed.
        printf("Derived specific message.\n");
    }
};

Нет шаблонов решения - редизайн

Еще один вариант - немного изменить код. ИМО это один на самом деле является предпочтительным решением, если вы абсолютно необходимо вызвать перекрытый PrintStartMessage изнутри MyBase строительства.

Это предложение состоит в том, чтобы отделить Derived от MyBase следующим образом:

class ICanPrintStartMessage {
public:
    virtual ~ICanPrintStartMessage() {}
    virtual void PrintStartMessage() = 0;
};

class MyBase {
public:
    MyBase(ICanPrintStartMessage *p) : _p(p) {
        _p->PrintStartMessage();
        printf("Doing MyBase initialization...\n");
    }

    ICanPrintStartMessage *_p;
};

class Derived : public ICanPrintStartMessage {
public:
    virtual void PrintStartMessage() { printf("Starting Derived!!!\n"); }
};

Вы инициализируете MyBase следующим образом:

int main() {
    Derived d;
    MyBase b(&d);
}

Ответ 4

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

Ответ 5

Если PrintStartMessage() не была чистой виртуальной функцией, а обычной виртуальной функцией, компилятор не стал бы жаловаться на нее. Однако вам все равно придется выяснить, почему производная версия PrintStartMessage() не вызывается.

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

Ответ 6

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

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

#include <iostream>

class MyBase
{
public:
    virtual void UniqueCode() = 0;
    MyBase() {};
    void init(MyBase & other)
    {
      std::cout << "Shared Code before the unique code" << std::endl;
      other.UniqueCode();
      std::cout << "Shared Code after the unique code" << std::endl << std::endl;
    }
};

class FirstDerived : public MyBase
{
public:
    FirstDerived() : MyBase() { init(*this); };
    void  UniqueCode()
    {
      std::cout << "Code Unique to First Derived Class" << std::endl;
    }
private:
    using MyBase::init;
};

class SecondDerived : public MyBase
{
public:
    SecondDerived() : MyBase() { init(*this); };
    void  UniqueCode()
    {
      std::cout << "Code Unique to Second Derived Class" << std::endl;
    }
private:
    using MyBase::init;
};

int main()
{
    FirstDerived first;
    SecondDerived second;
}

Выход:

 Shared Code before the unique code
 Code Unique to First Derived Class
 Shared Code after the unique code

 Shared Code before the unique code
 Code Unique to Second Derived Class
 Shared Code after the unique code