中间类:称为纯虚方法

时间:2015-06-23 18:40:10

标签: c++ inheritance polymorphism virtual

对于许多类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

  3. 对于更理智的设计的建议将不胜感激。

4 个答案:

答案 0 :(得分:2)

简答:

不要在构造函数中调用虚函数。这样做会让你陷入困境。


更长的回答:

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

调用B::B()时,它会调用虚函数doSomething(),在这种情况下调用B::doSomething()。还没有问题;该功能存在。问题在于doSomethingHelper()正文中对B::doSomething()的调用。那个功能在这一点上是纯虚拟的。如上所述,没有迹象表明该对象最终将成为class C的实例。无法调用无法调用的函数C::doSomethingHelper(),无法从B::B()A::A()调用。没有A::doSomethingHelper(),没有B::doSomethingHelper(),因此功能不存在。你是干杯。


  

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

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

答案 1 :(得分:1)

根据标准:

  

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&#39的构造函数触发两阶段初始化:

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. Demo here的成员。

答案 2 :(得分:1)

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

B的构造函数正在运行时,尚未创建C子对象,因此不能使用其任何覆盖函数。由于B没有为doSomethingHelper提供实施,因此无法做任何明智的事。

答案 3 :(得分:0)

将所有复杂性移至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初始化这些成员。