2010-01-12 61 views
2

我正在使用遗留的框架。可以说'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; 
}; 

但是,这是在初始化列表中初始化的类成员,因为他们尚未执行(FE“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++米this链接。 Konstantin Oznobikhin给出了一个很好的通用解决方案。

+2

即UB(未定义行为)和一个相当恶劣的伎俩编译器播放。 – 2010-01-12 08:29:01

回答

3

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

我已经看到过这个过程,为开发人员提供了一些痛苦:扩展ACE_Task类以提供一个周期性线程,该周期性线程可以用具体功能进行扩展,并仅从周期性线程构造函数中激活线程才能发现在测试中并且往往不是它的工作,但是在某些情况下,线程实际上是在大多数派生对象被初始化之前启动的。

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

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

你可以尝试的另一件事(这是脆弱的,我会建议不要)将B类打破成独立于A的C类和继承B类的B类,首先来自C然后来自A那里有巨大的警告评论)。这将保证C将在A之前构造。然后使C子对象成为A的参数(通过接口或作为模板参数)。这可能是最快的黑客攻击,但不是一个好的攻击。一旦你愿意修改代码,就做对了。

+0

感谢您的建议。看起来像是我在商店里进行的一个重要的重构练习。 – Abhay 2010-01-12 09:56:42

+0

@dribeas:你不仅了解真正的生产代码相关问题(不像一些人认为这是一个愚蠢的问题),也提供了很好的建议,其中一个我用于临时修复。所以我接受这个答案。谢谢。 – Abhay 2010-01-13 15:02:54

2

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

一个解决方案可能是将一个辅助对象从派生类传递给基类的构造函数。

+0

它不能帮助它!它是一个'传统'应用框架。我也考虑过帮助对象的方法,但在我的情况下,这是太多的工作。让我们看看我们是否找到更好的选择。谢谢 – Abhay 2010-01-12 08:23:56

2

也许Lazy Initialization是为你做的。在A中存储一个标志,是否已初始化。每当你调用一个方法,检查标志。如果它是假的,则初始化A(B的运算符已经运行),并将该标志设置为true。

1

这是一个糟糕的设计,正如已经说过的那样,它是UB。请考虑将这种依赖于一些其他的方法从派生类的构造说“初始化”,并呼吁该initialize方法(或之前的任何地方,你真正需要的基类数据进行初始化)

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

虽然很详细,我想这应该安全地假继承,并让你等待构建后才B已经完成它的事情。它也被封装,所以当你可以拉你不应该改变比A和B之外的任何

或者,您可能还需要采取退后几步,并问自己;继承给我的功能是什么,我正在尝试使用,以及如何通过其他方式来实现?例如,CRTP可以用作virtual的替代方案,而策略则是函数继承的替代方案。 (我认为这是正确的措词)。我在上面使用这些想法,只是放弃模板B/C,我只希望A在B上模板,反之亦然。