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() ) { ... }
    };