施工后立即调用虚拟方法

时间:2010-10-20 19:50:39

标签: c++ inheritance

我需要在构造派生对象之后立即为从给定基类派生的所有类调用虚方法。但是在基类构造函数中执行此操作将导致纯虚方法调用

这是一个简化的例子:

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);
    }
};

我可以在load构造函数中调用Derived,但Derived无法知道如何创建Loader。任何想法/解决方法?

8 个答案:

答案 0 :(得分:6)

问题是基类构造发生在完全构造派生类之前。您应该从派生类调用“load”,初始化throguh不同的虚拟成员函数或创建辅助函数来执行此操作:

Base* CreateDerived()
{
    Base* pRet = new Derived;
    pRet->Load();
    return pRet;
}

答案 1 :(得分:3)

C ++ FAQ将此问题称为 DBDI 构建期间的动态绑定。主要是,问题是避免在这里提出其他答案所倡导的邪恶的两阶段建设。这是“我的”常见问题项目 - 我说服Marshall添加它。

然而,Marshall对它的看法非常笼统(这对常见问题解答很好),而我则更关注特定的设计/编码模式。

因此,我没有将您发送到常见问题解答,而是将您发送到我自己的博客,文章"How to avoid post-construction by using Parts Factories",该文章链接到相关的常见问题项目,但深入讨论了该模式。

你可以跳过前两段...

我在那里漫步。 : - )

干杯&amp;第h。,

答案 2 :(得分:2)

使用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();
}

答案 3 :(得分:1)

无法在getLoader()课程中添加方法Base,以便DerivedClass构造函数可以在this上调用它来获得Loader? 由于DerivedClass构造函数将在Base类构造函数之后调用,因此应该可以正常工作。

答案 4 :(得分:1)

除非你告诉我们你想要完成什么,而不是如何做,否则很难给出建议。我发现从工厂构造这样的对象通常会更好,它会先加载所需的数据,然后将数据传递给对象的构造函数。

答案 5 :(得分:0)

许多已知的框架(如MFC)执行此操作:它们创建(虚拟)成员函数Init()或Create()并在那里进行初始化,然后在用户调用它的文档中强制执行。我知道你不会喜欢这个想法,但是你不能从构造函数中调用虚方法并期望它具有多态性,无论方法纯粹......

答案 6 :(得分:0)

有很多方法可以解决这个问题,这里有1个建议适合您提供的框架

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);
    }
};

答案 7 :(得分:0)

在其他答案之后可能会有点迟到,但我还是会尝试一下。

可以实施安全而不更改派生类。但是,您需要更改所有这些类的使用,这可能会更糟糕,具体取决于您的方案。如果你还在设计,那么这可能是可行的选择。

基本上,您可以应用curiously recurring template pattern并在调用构造函数后注入初始化代码。此外,如果您按照下面的说法进行操作,甚至可以保护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移动到构造函数,并将加载器作为参考参数提供默认值,如下所示:

struct Derived : public Base {
     Derived ( Loader& loader = Loader() ) { ... }
}; 

这样,你完全避免了这个问题。

摘要:您的选择如下:

  1. 如果您不受外部约束的限制,并且根据此不具备广泛的代码库,请更改您的设计以获得更安全的信息。
  2. 如果您想保持load的方式并且不要过多地改变您的课程但愿意为更改所有实例化付出代价,请按照上面的建议应用CRTP。
  3. 如果您坚持主要向后兼容现有的客户端代码,则必须更改您的类以使用PIMPL,正如其他人所建议的那样或使用现有问题。