我有一个包含纯虚函数的基类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)" (?PrintStartMessage@MyBase@@UAEXXZ) referenced in function "public: __thiscall MyBase::MyBase(void)" (??0MyBase@@QAE@XZ)
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
我该怎么做?
6 回答
在该对象仍在构造时尝试从派生中调用纯抽象方法是不安全的 . 这就像试图将汽油填充到汽车中,但是那辆汽车仍在装配线上并且还没有放入油箱 .
你可以做的最接近的事情就是首先完全构造你的对象,然后在之后调用方法:
您无法以您想象的方式执行此操作,因为您无法从基类构造函数中调用派生的虚函数 - 该对象尚未属于派生类型 . 但你不需要这样做 .
在MyBase构造之后调用PrintStartMessage
我们假设你想做这样的事情:
所需的执行跟踪将是:
但这就是构造函数的用途!只是废弃虚函数并使派生的构造函数完成工作!
输出就是我们所期望的:
但是,这并不强制派生类显式实现
PrintStartMessage
功能 . 但另一方面,请三思而后行是否有必要,因为无论如何它们总是可以提供空的实现 .在MyBase构造之前调用PrintStartMessage
如上所述,如果要在
Derived
构造函数之前调用PrintStartMessage
,则无法完成此操作,因为还没有Derived
对象可以调用PrintStartMessage
. 要求PrintStartMessage
成为非静态成员是没有意义的,因为它无法访问任何Derived
数据成员 .具有工厂功能的静态功能
或者我们可以使它像一个静态成员:
一个自然的问题是它将如何被称为?
我可以看到两种解决方案:一种类似于@greatwolf,你必须手动调用它 . 但是现在,因为它是一个静态成员,所以你可以在构造
MyBase
的实例之前调用它:输出将是
此方法会强制所有派生类实现
PrintStartMessage
. 不幸的是,只有当我们用我们的工厂功能构建它们时才会这样......这是这个解决方案的一个巨大缺点 .第二种解决方案是采用奇怪的重复模板模式(CRTP) . 通过在编译时告诉
MyBase
完整的对象类型,它可以在构造函数中进行调用:输出符合预期,无需使用专用工厂功能 .
使用CRTP从PrintStartMessage中访问MyBase
当
MyBase
正在执行时,它已经可以访问其成员 . 我们可以让PrintStartMessage
能够访问调用它的MyBase
:以下也是有效且经常使用的,虽然有点危险:
没有模板解决方案 - 重新设计
还有一种选择是重新设计你的代码 . IMO这个实际上是首选的解决方案,如果你必须从
MyBase
构造中调用一个被覆盖的PrintStartMessage
.此提案将
Derived
与MyBase
分开,如下所示:您初始化
MyBase
如下:您不应该在构造函数中调用
virtual
函数 . Period . 你必须找到一些解决方法,比如制作PrintStartMessage
非virtual
并在每个构造函数中显式调用 .如果PrintStartMessage()不是纯虚函数而是普通虚函数,编译器就不会抱怨它 . 但是你仍然需要弄清楚派生的原因未调用PrintStartMessage()的版本 .
由于派生类在其自己的构造函数之前调用基类的构造函数,因此派生类的行为类似于基类,因此调用基类的函数 .
有很多文章解释了为什么你永远不应该在构造函数和析构函数中调用虚函数 . 看一下here和here,了解在此类调用过程中幕后发生的情况 .
简而言之,对象是从基础到派生的构造的 . 因此,当您尝试从基类构造函数调用虚函数时,尚未发生从派生类重写,因为尚未调用派生构造函数 .
我知道这是一个老问题,但在处理我的程序时我遇到了同样的问题 .
如果您的目标是通过让Base类处理共享初始化代码同时要求Derived类在纯虚方法中指定它们唯一的代码来减少代码重复,那么这就是我所决定的 .
输出是: