C++ 构造后立即调用虚拟方法
在构造派生对象之后,我需要为从给定基类派生的所有类调用虚拟方法。但是在基类构造函数中这样做将导致一个纯虚拟方法调用 以下是一个简化的示例:C++ 构造后立即调用虚拟方法,c++,inheritance,C++,Inheritance,在构造派生对象之后,我需要为从给定基类派生的所有类调用虚拟方法。但是在基类构造函数中这样做将导致一个纯虚拟方法调用 以下是一个简化的示例: struct Loader { int get(int index) { return 0; } }; struct Base{ Base() { Loader l; load( l ); // <-- pure virtual call! } virtual void load( L
struct Loader {
int get(int index) { return 0; }
};
struct Base{
Base() {
Loader l;
load( l ); // <-- pure virtual call!
}
virtual void load( Loader & ) = 0;
};
struct Derived: public Base {
int value;
void load( Loader &l ) {
value = Loader.get(0);
}
};
struct加载器{
int get(int index){返回0;}
};
结构基{
Base(){
装载机l;
load(l);//许多已知的框架(如MFC)都是这样做的:它们生成(虚拟)成员函数Init()或Create()然后在那里进行初始化,然后在文档中要求用户调用它。我知道你不会喜欢这个想法,但你不能从构造函数中调用虚拟方法并期望它以多态方式运行,不管方法的纯度如何…问题是基类构造发生在派生类I之前完全构造。您应该从派生类调用“load”,通过其他虚拟成员函数初始化,或者创建一个帮助函数来执行此操作:
Base* CreateDerived()
{
Base* pRet = new Derived;
pRet->Load();
return pRet;
}
使用PIMPL模式:
template<typename T>
class Pimpl
{
public:
Pimpl()
{
// At this point the object you have created is fully constructed.
// So now you can call the virtual method on it.
object.load();
}
T* operator->()
{
// Use the pointer notation to get access to your object
// and its members.
return &object;
}
private:
T object; // Not technically a pointer
// But otherwise the pattern is the same.
// Modify to your needs.
};
int main()
{
Pimpl<Derived> x;
x->doStuff();
}
模板
类Pimpl
{
公众:
Pimpl()
{
//此时,您创建的对象已完全构建。
//现在你可以调用虚拟方法了。
load();
}
T*运算符->()
{
//使用指针表示法访问对象
//及其成员。
返回&对象;
}
私人:
T object;//从技术上讲不是指针
//但除此之外,模式是相同的。
//根据您的需要进行修改。
};
int main()
{
PIMPLx;
x->doStuff();
}
C++ FAQ调用这个问题<强> dBD<强>动态绑定,主要是为了避免在其他答案中所提倡的邪恶的两阶段构造,这是一个“我的”常见问题,我说服Marshall添加它。
然而,Marshall对它的理解是非常笼统的(这对于常见问题解答很有用),而我更关心的是特定的设计/编码模式
因此,我没有将您发送到常见问题解答,而是将您发送到我自己的博客,这篇文章链接到相关的常见问题解答项目,但深入讨论了该模式
你可以跳过前两段
我在那里闲逛了一会儿。:-)
干杯,你能不能在基类
中添加一个方法getLoader()
,这样派生类
构造函数就可以调用这个
来获得一个加载器
?
由于DerivedClass
构造函数将在Base
类构造函数之后调用,这应该可以很好地工作。除非您告诉我们您试图完成什么,而不是如何完成,否则很难给出建议。我发现通常最好从工厂构造这样的对象,工厂将提前加载所需的数据,并且当将数据传递到对象的构造函数中时。有很多方法可以纠正这一点,这里有一个建议适合您提供的框架
struct Loader {
int get(int index) { return 0; }
};
struct Base{
Base() {
}
Loader & getLoader( );
private:
Loader l;
};
struct Derived: public Base {
int value;
Derived( ) {
value = getLoader().get(0);
}
};
在其他答案之后,这可能会晚一点出现,但我还是会尝试一下
您可以安全地实现此,并且无需更改派生类。但是,您需要更改所有这些类的用法,这可能更糟糕,具体取决于您的场景。如果您仍在设计,那么这可能是可行的替代方案
基本上,您可以在调用构造函数后应用并注入初始化代码。此外,如果您按照我在下面编写的那样执行,您甚至可以保护load
不被调用两次
struct Loader {
int get(int index) { return 0; }
};
struct Base {
virtual ~Base() {} // Note: don't forget this.
protected:
virtual void load( Loader & ) = 0;
};
struct Derived : public Base {
int value;
protected:
void load( Loader &l ) {
value = l.get(0);
}
};
template<typename T>
class Loaded : public T
{
public:
Loaded () {
Loader l; T::load(l);
}
};
int main ( int, char ** )
{
Loaded<Derived> derived;
}
这样,您就完全避免了问题
摘要:您的选择如下:
如果您不受外部约束的限制,并且没有依赖于此的广泛代码库,请更改您的设计以获得更安全的设计
如果您想保持load
的原样,不想太多地更改类,但愿意为更改所有实例化付出代价,请按照上面的建议应用CRTP
如果您坚持与现有客户机代码向后兼容,那么您将不得不更改您的类以使用其他人建议的PIMPL,或者接受现有的问题
@Benoit:不是在构造函数中。@Vargas:它可能设计得更好,所以您没有这种依赖关系。例如,为什么load
是在构造函数中被调用的一个单独的函数?为什么不让Derived
加载它自己的值。当您需要先调用构造函数,然后再调用某个init函数时,就会调用它两阶段构造,最好隐藏在接口后面,因为类的用户很容易出错。请您在问题中加入更多的上下文。您想做什么?而不是如何做。使用工厂模式。您不能使用组合而不是继承吗?如果不迫切需要使用继承,则组合将让这变得微不足道。好吧,我成功地做到了这一点,但是我如何才能防止派生被创建为elsewere,因此在没有正确初始化的情况下被使用?@Vargas:对于从Base派生的每个类,使构造函数私有并定义一个Base*CreateDerivedX()
并让它成为朋友。@Eugen,这不需要更改每个派生类就可以做到吗?@Vargas:不,你唯一可以简化的是有一个模板化的CreateDerivedX。其余的(私有ctor&friend CreateDerivedX)你需要为每个从基派生的类做。@Goz:就像我写这篇文章时一样,上面的代码会泄漏内存(可能还有其他资源)如果Load
抛出异常。可以修复该特定问题,但该问题只是两阶段构造的绝对脆弱性的一个典型表现。要吗
struct Derived : public Base {
Derived ( Loader& loader = Loader() ) { ... }
};