her*_*ian 35 c++ constructor abstract-class pure-virtual object-lifetime
我有一个包含纯虚函数的基类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;
}
Run Code Online (Sandbox Code Playgroud)
但我收到链接器错误.
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)
Run Code Online (Sandbox Code Playgroud)
我想强制所有派生类来...
A- implement it
B- call it in their constructor
Run Code Online (Sandbox Code Playgroud)
我该怎么做?
gre*_*olf 16
在该对象仍在构造时尝试从派生中调用纯抽象方法是不安全的.这就像试图将汽油填充到汽车中,但该汽车仍在装配线上并且尚未放入油箱.
你可以做的最接近的事情就是首先完全构造你的对象,然后在之后调用方法:
template <typename T>
T construct_and_print()
{
T obj;
obj.PrintStartMessage();
return obj;
}
int main()
{
Derived derived = construct_and_print<Derived>();
}
Run Code Online (Sandbox Code Playgroud)
您无法以您想象的方式执行此操作,因为您无法从基类构造函数中调用派生的虚函数 - 该对象尚未属于派生类型.但你不需要这样做.
我们假设你想做这样的事情:
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"); }
};
Run Code Online (Sandbox Code Playgroud)
所需的执行跟踪将是:
Doing MyBase initialization...
Starting Derived!
Run Code Online (Sandbox Code Playgroud)
但这就是构造函数的用途!只是废弃虚函数并使派生的构造函数完成工作!
class MyBase {
public:
MyBase() { printf("Doing MyBase initialization...\n"); }
};
class Derived : public MyBase {
public:
Derived() { printf("Starting Derived!\n"); }
};
Run Code Online (Sandbox Code Playgroud)
输出就是我们所期望的:
Doing MyBase initialization...
Starting Derived!
Run Code Online (Sandbox Code Playgroud)
但是,这并不强制派生类显式实现该Derived功能.但另一方面,请三思而后行是否有必要,因为无论如何它们总是可以提供空的实现.
如上所述,如果要在构造函数PrintStartMessage之前调用PrintStartMessage,则无法完成此操作,因为还没有要调用的Derived对象Derived.要求PrintStartMessage成为非静态成员是没有意义的,因为它无法访问任何PrintStartMessage数据成员.
或者,我们可以使它像这样的静态成员:
class MyBase {
public:
MyBase() {
printf("Doing MyBase initialization...\n");
}
};
class Derived : public MyBase {
public:
static void PrintStartMessage() { printf("Derived specific message.\n"); }
};
Run Code Online (Sandbox Code Playgroud)
一个自然的问题是它将如何被称为?
我可以看到两种解决方案:一种类似于@greatwolf,你必须手动调用它.但是现在,因为它是一个静态成员,所以你可以Derived在构造一个实例之前调用它:
template<class T>
T print_and_construct() {
T::PrintStartMessage();
return T();
}
int main() {
Derived derived = print_and_construct<Derived>();
}
Run Code Online (Sandbox Code Playgroud)
输出将是
Derived specific message.
Doing MyBase initialization...
Run Code Online (Sandbox Code Playgroud)
这种方法确实强制实现所有派生类MyBase.不幸的是,只有当我们用我们的工厂功能构建它们时才会这样......这是这个解决方案的一个巨大缺点.
第二种解决方案是采用奇怪的重复模板模式(CRTP).通过PrintStartMessage在编译时告诉完整的对象类型,它可以在构造函数中进行调用:
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"); }
};
Run Code Online (Sandbox Code Playgroud)
输出符合预期,无需使用专用工厂功能.
在MyBase执行时,它已经可以访问其成员.我们可以MyBase访问PrintStartMessage调用它的那个:
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");
}
};
Run Code Online (Sandbox Code Playgroud)
以下也是有效且经常使用的,虽然有点危险:
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");
}
};
Run Code Online (Sandbox Code Playgroud)
还有一种选择是重新设计你的代码.IMO这个实际上是首选的解决方案,如果你绝对必须MyBase从PrintStartMessage构造中调用一个被覆盖的.
这个建议是分开MyBase的Derived,如下:
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"); }
};
Run Code Online (Sandbox Code Playgroud)
您初始化MyBase如下:
int main() {
Derived d;
MyBase b(&d);
}
Run Code Online (Sandbox Code Playgroud)
您不应该virtual在构造函数中调用函数.期间.你必须找到一些解决方法,比如make PrintStartMessagenon virtual和在每个构造函数中显式调用.