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

发布于 2024-08-17 17:20:12 字数 1791 浏览 3 评论 0原文

我正在开发一个遗留框架。假设“A”是基类,“B”是派生类。这两个类都会执行一些关键的框架初始化。 FWIW,它大量使用 ACE 库。

我有一种情况;创建了“B”的实例。但是“A”的构造函数依赖于一些只能从“B”执行的初始化。

正如我们所知,当实例化“B”时,“A”的构造函数会在“B”的构造函数之前被调用。 virtual 机制在 ctor 中不起作用,使用 static 函数 被排除(由于 静态初始化顺序失败)。

我考虑使用 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 给出了一个很好的通用解决方案。

I am working on a legacy framework. Lets say 'A' is the base-class and 'B' is the derived class. Both the classes do some critical framework initialization. FWIW, it uses ACE library heavily.

I have a situation wherein; an instance of 'B' is created. But the ctor of 'A' depends on some initialization that can only be performed from 'B'.

As we know when 'B' is instantiated the ctor for 'A' is invoked before that of 'B'. The virtual mechanism dosen't work from ctors, using static functions is ruled-out (due to static-initialization-order-fiasco).

I considered using the CRTP pattern as follows :-

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

But the class members that are initialized in the initializer list have undefined values as they are not yet executed (f.e. 'a' in the above case). In my case there a number of such framework-based initialization variables.

Are there any well-known patterns to handle this situation?

Thanks in advance,


Update:

Based on the idea given by dribeas, i conjured-up a temporary solution to this problem (a full-fledged refactoring does not fit my timelines for now). The following code will demonstrate the same:-

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

}; 

For some more discussion on the same see this link on c.l.c++.m. There is a nice generic solution given by Konstantin Oznobikhin.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

望她远 2024-08-24 17:20:12

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

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

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

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

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

Probably the best thing you can do is refactoring. It does not make sense to have a base class depend on one of its derived types.

I have seen this done before, providing quite some pain to the developers: extend the ACE_Task class to provide a periodic thread that could be extended with concrete functionality and activating the thread from the periodic thread constructor only to find out that while in testing and more often than not it worked, but that in some situations the thread actually started before the most derived object was initialized.

Inheritance is a strong relationship that should be used only when required. If you take a look at the boost thread library (just the docs, no need to enter into detail), or the POCO library you will see that they split the problem in two: thread classes control thread execution and call a method that is passed to them in construction: the thread control is separated from the actual code that will be runned, and the fact that the code to be run is received as an argument to the constructor guarantees that it was constructed before the thread constructor was called.

Maybe you could use the same approach in your own code. Divide the functionality in two, whatever the derived class is doing now should be moved outside of the hierarchy (boost uses functors, POCO uses interfaces, use whatever seems to fit you most). Without a better description of what you are trying to do, I cannot really go into more detail.

Another thing you could try (this is fragile and I would recommend against) is breaking the B class into a C class that is independent of A and a B class that inherits from both, first from C then from A (with HUGE warning comments there). This will guarantee that C will be constructed prior to A. Then make the C subobject an argument of A (through an interface or as a template argument). This will probably be the fastest hack, but not a good one. Once you are willing to modify the code, just do it right.

最冷一天 2024-08-24 17:20:12

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

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

First, I think your design is bad if the constructor of a base class depends on the something done in the constructor in a derived. It really shouldn't be that way. At the time the constructor of the base class run, the object of the derived class basically doesn't exist.

A solution might be to have a helper object passed from the derived class to the constructor of the base class.

云柯 2024-08-24 17:20:12

也许延迟初始化可以为您做到这一点。在 A 中存储一个标志,无论它是否已初始化。每当您调用方法时,请检查该标志。如果为 false,则初始化 A(此时 B 的 ctor 已运行)并将标志设置为 true。

Perhaps Lazy Initialization does it for you. Store a flag in A, wether it's initialized or not. Whenever you call a method, check for the flag. if it's false, initialize A (the ctor of B has been run then) and set the flag to true.

半夏半凉 2024-08-24 17:20:12

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

It is a bad design and as already said it is UB. Please consider moving such dependencies to some other method say 'initialize' and call this initialize method from your derived class constructor (or anywhere before you actually need the base class data to be initialized)

夏了南城 2024-08-24 17:20:12

唔。因此,如果我正确地理解了这一点,“A”是遗留代码的一部分,并且您非常确定某些问题的正确答案是使用派生类 B。

在我看来,最简单的方法 是使用派生类 B。解决方案可能是创建一个函数式(非 OOP)风格的静态工厂函数;

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

除了你说你遇到了静态初始化顺序惨败?我认为您不会采用这种设置,因为没有进行初始化。

好吧,更多地看看这个问题,“C”需要“B”完成一些“A”需要完成的设置,只有“A”获得优先权,因为你想要继承。那么...假继承,以一种可以让您控制构造顺序的方式...?

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。它也是封装的,因此当您可以拉取 A 时,除了 A 和 B 之外,您不必更改任何其他内容。

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

Hmm. So, if I'm reading into this correctly, "A" is part of the legacy code, and you're pretty damn sure the right answer to some problem is to use a derived class, B.

It seems to me that the simplest solution might be to make a functional (non-OOP) style static factory function;

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

Except that you say you run into static initialization order fiasco? I wouldn't think you would with this kind of setup, since there's no initialization going on.

Alright, looking at the problem more, "C" needs to have some setup done by "B" that "A" needs done, only "A" gets first dibs, because you want to have inheritance. So... fake inheritance, in a way that lets you control construction order...?

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

While it's verbose, I think this should safely fake inheritance, and allow you to wait to construct A until after B has done it's thing. It's also encapsulated, so when you can pull A you shouldn't have to change anything other than A and B.

Alternatively, you may also want to take a few steps back and ask yourself; what is the functionality that inheritance gives me that I am trying to use, and how might I accomplish that via other means? For instance, CRTP can be used as an alternative to virtual, and policies an alternative to function inheritance. (I think that's the right phrasing of that). I'm using these ideas above, just dropping the templates b/c I'm only expecting A to template on B and vice versa.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文