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

}; 

有关相同内容的更多讨论,请参见clc++.m上的此链接。Konstantin Oznobikhin 给出了一个很好的通用解决方案。

4

5 回答 5

3

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

我以前见过这样做,给开发人员带来了相当多的痛苦:扩展 ACE_Task 类以提供一个可以扩展具体功能的周期性线程,并从周期性线程构造函数中激活线程只是为了在测试时发现这一点等等通常它会起作用,但在某些情况下,线程实际上是在初始化最多派生对象之前启动的。

继承是一种强关系,只应在需要时使用。如果您查看 boost 线程库(只是文档,无需详细说明)或 POCO 库,您会发现它们将问题分为两部分:线程类控制线程执行并调用传递的方法在构造过程中对他们来说:线程控制与将要运行的实际代码分开,并且要运行的代码作为构造函数的参数被接收这一事实保证了它是在调用线程构造函数之前构造的。

也许您可以在自己的代码中使用相同的方法。将功能一分为二,无论派生类现在做什么都应该移到层次结构之外(boost 使用函子,POCO 使用接口,使用最适合您的任何东西)。如果没有更好地描述您正在尝试做的事情,我无法真正详细说明。

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

于 2010-01-12T08:27:21.327 回答
2

首先,如果基类的构造函数依赖于派生类的构造函数中所做的事情,我认为你的设计很糟糕。真的不应该这样。在基类的构造函数运行时,派生类的对象基本不存在。

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

于 2010-01-12T08:21:12.743 回答
2

也许延迟初始化会为您完成。在 A 中存储一个标志,无论它是否已初始化。每当您调用方法时,请检查标志。如果为假,则初始化 A(B 的 ctor 已经运行)并将标志设置为真。

于 2010-01-12T08:27:47.677 回答
1

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

于 2010-01-12T09:18:29.243 回答
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 上进行模板,反之亦然。

于 2010-01-12T15:26:52.783 回答