对派生类构造函数问题的依赖性

时间:2010-01-12 08:14:48

标签: c++ constructor initialization crtp

我正在研究遗留框架。让我们说'A'是基类,'B'是派生类。这两个类都做了一些关键的框架初始化。 FWIW,它大量使用ACE库。

我有一种情况:创建'B'的实例。但是'A'的ctor取决于一些只能从'B'执行的初始化。

正如我们所知,当'B'被实例化时,'A'的ctor在'B'之前被调用。 virtual机制无法使用ctors,static functions被排除(由于static-initialization-order-fiasco)。

我考虑使用CRTP模式如下: -

template<class Derived>
class A {
public:
  A(){
    static_cast<Derived*>(this)->fun();
  }
};

class B : public A<B> {
public:
  B() : a(0) {
    a = 10;
  }
  void fun() { std::cout << "Init Function, Variable a = " << a << std::endl; }
private:
  int a;
};

但是在初始化列表中初始化的类成员具有未定义的值,因为它们尚未执行(在上面的例子中为'a')。在我的例子中,有许多这样的基于框架的初始化变量。

是否有任何众所周知的模式来处理这种情况?

提前致谢,


更新

基于dribeas给出的想法,我想出了一个临时解决这个问题的方法(一个完整的重构现在不符合我的时间表)。以下代码将演示相同的内容: -

// move all A's dependent data in 'B' to a new class 'C'.
class C {
public:
   C() : a(10)
   {  }
   int getA() { return a; }
private:
   int a;

};

// enhance class A's ctor with a pointer to the newly split class
class A {
public:
   A(C* cptr)
   {
     std::cout << "O.K. B's Init Data From C:- " << cptr->getA() <<
std::endl;
   }

};

// now modify the actual derived class 'B' as follows
class B : public C, public A {
public:
   B()
     : A(static_cast<C*>(this))
   { }

}; 

有关c.l.c ++。m上相同的this链接的更多讨论。 Konstantin Oznobikhin给出了一个很好的通用解决方案。

5 个答案:

答案 0 :(得分:3)

你可以做的最好的事情就是重构。让基类依赖于其派生类型之一是没有意义的。

我之前已经看过这个,给开发人员带来了一些痛苦:扩展ACE_Task类以提供一个可以使用具体功能进行扩展的周期性线程,并从周期性线程构造函数中激活线程,只是为了找到测试,而且往往是有效的,但在某些情况下,线程实际上是在最初派生的对象被初始化之前开始的。

继承是一种强关系,只有在需要时才能使用。如果你看看boost线程库(只是文档,不需要详细说明),或POCO库,你会看到他们将问题分成两部分:线程类控制线程执行并调用传递的方法对于它们的构造:线程控件与将要运行的实际代码分开,并且要运行的代码作为构造函数的参数接收的事实保证它是在调用线程构造函数之前构造的。

也许您可以在自己的代码中使用相同的方法。将功能分为两部分,无论派生类现在做什么,都应该移到层次结构之外(boost使用仿函数,POCO使用接口,使用最适合你的东西)。如果没有更好地描述你想要做什么,我就不能真正了解更多细节。

你可以尝试的另一件事(这是脆弱的,我建议反对)是将B类分解为一个独立于A的C类和一个继承自两者的B类,首先来自C然后来自A(使用HUGE)警告评论那里)。这将保证C将在A之前构造。然后使C子对象成为A的参数(通过接口或作为模板参数)。这可能是最快的黑客,但不是一个好的黑客。一旦您愿意修改代码,只需正确执行即可。

答案 1 :(得分:2)

首先,如果基类的构造函数依赖于派生类中构造函数中完成的内容,我认为您的设计很糟糕。它真的不应该那样。在运行基类的构造函数时,派生类的对象基本上不存在。

解决方案可能是将派生类从派生类传递给基类的构造函数。

答案 2 :(得分:2)

也许Lazy Initialization为你做了。在A中存储一个标志,是否已经初始化。无论何时调用方法,都要检查标志。如果它是假的,则初始化A(然后运行B的ctor)并将标志设置为true。

答案 3 :(得分:1)

这是一个糟糕的设计,正如已经说过的那样是UB。请考虑将这些依赖项移动到其他方法,例如'initialize',并从派生类构造函数中调用此初始化方法(或者在实际需要初始化基类数据之前的任何位置)

答案 4 :(得分:0)

嗯。所以,如果我正确地读到这个,“A”是遗留代码的一部分,而且你非常确定某个问题的正确答案是使用派生类B.

在我看来,最简单的解决方案可能是制作一个功能性(非OOP)风格的静态工厂函数;

static B& B::makeNew(...);

除非你说你遇到静态初始化命令惨败?我不认为你会用这种设置,因为没有初始化。

好的,更多地看问题,“C”需要通过“B”进行一些设置,“A”需要完成,只有“A”获得第一个dib,因为你想拥有继承。所以......假继承,让你控制施工顺序......?

class A
{
    B* pB;
public:
    rtype fakeVirtual(params) { return pB->fakeVirtual(params); }

    ~A()
    {
        pB->deleteFromA();
        delete pB;
        //Deletion stuff
    }

protected:
    void deleteFromB()
    {
        //Deletion stuff
        pB = NULL;
    }
}

class B
{
    A* pA;
public:
    rtype fakeInheritance(params) {return pA->fakeInheritance(params);}

    ~B()
    {
        //deletion stuff
        pA->deleteFromB();
    }

protected:
    friend class A;
    void deleteFromA()
    {
        //deletion stuff
        pA = NULL;
    }
}

虽然它很冗长,但我认为这应该可以安全地伪造继承,并允许你等到构建A直到B完成它之后。它也是封装的,所以当你可以拉A时,你不应该改变A和B以外的任何东西。

或者,您可能还想退几步并问自己;继承给我的功能是什么,我试图使用,以及如何通过其他方式实现这一点?例如,CRTP可以用作virtual的替代,而策略可以替代函数继承。 (我认为这是正确的措辞)。我正在使用上面的这些想法,只是删除模板b / c我只期望A到B上的模板,反之亦然。