Вы не можете сделать это так, как представляете себе, потому что вы не можете вызывать производные виртуальные функции из конструктора базового класса - объект еще не имеет производного типа.Но вам не нужно этого делать.
Вызов 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
.К сожалению, это верно только тогда, когда мы конструируем их с помощью нашей заводской функции ... что является огромным недостатком этого решения.
Второе решение - прибегнуть к шаблону Curily Recurring Template Pattern (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");
}
};
Нет решения для шаблонов - редизайн
Еще один вариант - немного изменить код.IMO, это действительно предпочтительное решение, если вам абсолютно необходимо вызвать переопределенную 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);
}