你不能按照你想象的方式去做,因为你不能从基类构造函数中调用派生虚函数——对象还不是派生类型。但你不需要这样做。
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
如上所述,如果您想在构建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
。不幸的是,只有当我们用我们的工厂函数构造它们时它才是真的……这是这个解决方案的一个巨大缺点。
第二种解决方案是求助于奇怪重复模板模式 (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");
}
};
无模板解决方案——重新设计
另一种选择是稍微重新设计您的代码。PrintStartMessage
如果您绝对必须从MyBase
构造中调用重写,则 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);
}