你不能按照你想象的方式去做,因为你不能从基类构造函数中调用派生虚函数——对象还不是派生类型。但你不需要这样做。
MyBase 构建后调用 PrintStartMessage
假设你想做这样的事情:
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 功能。但另一方面,请三思是否有必要,否则他们总是可以提供一个空的实现。
MyBase 构建前调用 PrintStartMessage
如上所述,如果您想在构造 Derived 之前调用 PrintStartMessage,则无法完成此操作,因为尚未调用 PrintStartMessage 的 Derived 对象。要求 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。不幸的是,只有当我们使用工厂函数构造它们时才会这样......这是这个解决方案的一个巨大缺点。
第二种解决方案是求助于好奇重复模板模式 (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"); }
};
输出符合预期,无需使用专门的工厂函数。
使用 CRTP 从 PrintStartMessage 中访问 MyBase
在执行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");
}
};
无模板解决方案——重新设计
另一种选择是稍微重新设计您的代码。如果您绝对必须从 MyBase 构造中调用覆盖的 PrintStartMessage,则 IMO 实际上是首选解决方案。
本提案是将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);
}