Вызов чистой виртуальной функции из конструктора базового класса
У меня есть базовый класс 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
Как я должен это делать?
Ответы
Ответ 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