繁体   English   中英

中级类:纯虚方法调用

[英]intermediate class: pure virtual method called

对于许多类C1C2等,初始化看起来是相等的,并且每个类之间只有一点点不同。 因此,我创建了承载初始化的基类B ,例如:

class B {
  public:
    B()
    {
      // complex initializations
      int a = doSomething();
      // more complex stuff with `a`
    };

    virtual int doSomething()
    {return 2 * doSomethingHelper();}

  protected:
    virtual int doSomethingHelper() = 0;
};

class C: public B {
  protected:
    virtual int doSomethingHelper()
    {return 1;}
};

int main() {
  C c;
  return 0;
}

这段代码失败了

pure virtual method called
terminate called without an active exception
Aborted (core dumped)

因为doSomethingHelper()用于初始化B

我想知道是否有更好的设计。 我的目标是:

  1. 使C的用户界面尽可能简单: C没有构造函数参数。

  2. 使C本身尽可能地简化,以使B其他具体化简短。 理想情况下,它仅包含doSomethingHelper

对于更合理设计的建议将不胜感激。

简短答案:

不要从构造函数中调用虚函数。 这样做会给您带来麻烦。


更长的答案:

声明C c分步创建和构造class C的实例。 该对象首先被构造为class A对象,然后构造为class B对象,最后构造为class C对象。 在调用B::B()的时候,没有任何想法认为该对象最终将是class C的实例。

调用B::B() ,它将调用虚拟函数doSomething() ,在这种情况下,这意味着调用B::doSomething() 没问题 该功能存在。 问题在于在B::doSomething()主体内对doSomethingHelper()的调用。 在这一点上,该功能是纯虚拟的。 如上所述,没有迹象表明该对象最终将是class C的实例。 无法调用函数C::doSomethingHelper()不能从B::B()A::A()调用。 没有A::doSomethingHelper() ,没有B::doSomethingHelper() ,因此该功能不存在。 你烤面包


我想知道是否有更好的设计。

有很多更好的设计。 最简单的方法是不要从类B的构造函数中调用doSomething() 。将该调用移至类C的构造函数。即使那样,从构造函数中调用虚函数也可能不是一个好主意。 如果类D从类C继承并覆盖C::doSomethingHelper()怎么办? 将通过调用C::doSomethingHelper()而不是D::doSomethingHelper()来构造D类的实例。

根据标准:

10.4 / 6:成员函数可以从抽象类的构造函数(或析构函数)中调用; 对于从此类构造函数(或析构函数)创建(或销毁)的对象直接或间接对纯虚函数进行虚拟调用(10.3)的效​​果未定义

这是因为,当您构造C时:

  • 首先,使用B()构造函数构造子对象B。 那时,仍然使用的是B的虚拟功能。 您会收到此错误,因为此时尚未为其定义doSomethingHelper()。

  • 只有完成B()之后,虚函数C的虚函数才会激活。

两阶段初始化

只有通过两个阶段的初始化才能避免这种情况:首先进行构造,然后调用初始化函数。 并不像您期望的那样友好和用户友好。

class B {
public:
    B() { /* complex initializations */ }
    ...
protected:
    void init() {  //  the rest of the what you wanted in constructor 
        int a = doSomething();
        // more complex stuff with `a`
    }
};

然后可以通过C的构造函数触发两个阶段的初始化:

class C {
public: 
    C() : B() {  // first B is constructed
       init();   // then the body of C's constructor is executed
    }   
 ...
 };

两阶段初始化的变体

您可以使用一个小的变体来隐藏两阶段方法,并为用户提供更多自由来定义或不定义自己的构造函数。

在B中,定义一个辅助嵌套类:

protected:
    class initialiser {
    public: 
        initialiser(B*b) {b->init();}  // the constructor launches the second phase init
    };

在C中,您只需要添加一个受保护的成员变量:

class C:  public  B {
    ...
protected:
    B::initialiser bi{this}; // this triggers automaticcaly the second phase
    ...
};

该标准的规则确保了第一个B被构造和然后C.演示的成员这里

您不能在构造函数中对派生类使用动态调度。

B的构造函数运行时,尚未创建C子对象,因此无法使用其任何重写函数。 由于B不提供doSomethingHelper的实现,因此无法做任何明智的事情。

将所有复杂性移到B::doSomething ,并从继承链C的末尾调用该方法:

class B {
  public:
    B()
    {};

    virtual int doSomething()
    {
      // complex initializations
      int a = 2 * doSomethingHelper();
      // more complex stuff with `a`
      return a;
    }

  protected:
    virtual int doSomethingHelper() = 0;
};

class C: public B {
  public:
    C(): B()
    {
      int a = doSomething();
    }

  protected:
    virtual int doSomethingHelper()
    {return 1;}
};

int main() {
  C c;
  return 0;
}

这可能需要您对B以前的private成员进行protected ,以使它们可以由C初始化。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM