【问题标题】:Problem with abstract class inheritance and constructor at C++C ++中抽象类继承和构造函数的问题
【发布时间】:2020-02-10 07:40:20
【问题描述】:
class Base {
public:
    Base(int a) : a_(a) {
        //do something
        someMethod();
        //do something else
    };
protected:
    int a_;
    virtual void someMethod() = 0 {};
};

class Derived : Base {
public:
    Derived() {
        Base::Base(42);
    }
protected:
    void someMethod() override {
        //realisation
    }
};

int main() {
    Derived *obj = new Derived();
    delete obj;
}

这段代码不能工作有两个错误:需要基类的默认构造函数,以及由于使用抽象方法而无法调用基类的带参数构造函数

我的问题是,当我创建class Derived 的对象时,根本没有调用在class Derived 中实现的someMethod()。另外我不想使用class Base的默认构造函数,但是编译器在发誓。

如何更正我的代码以查看我想要的功能?

【问题讨论】:

  • someMethod(); 应该是编译器的一个大闪烁错误。如果不是,请尽快提交错误报告。还是您间接调用它? virtual void someMethod() = 0 {}; 也是格式错误的。您发布的代码有一些不相关的问题。我建议你解决它们,以避免这里的答案脱轨。
  • 简短的“解决方案”:永远不要从构造函数中调用虚函数。
  • 你不能从构造函数中调用虚函数。派生对象尚未创建。
  • @super 你可以从构造函数中调用虚函数。但它将被静态分派,即发送到正在构建的类中的实现。但如果函数是 pure 虚拟的,则不允许这样做。

标签: c++ inheritance constructor virtual abstract


【解决方案1】:

如何更正我的代码以查看我想要的功能?

  1. 去掉Base的构造函数中对纯虚函数的调用。

  2. 在替代它的派生类的构造函数中调用someMethod

  3. 为成员初始化程序列表中的Base 子对象提供初始化程序。如果您没有为基础提供初始化程序,它将被默认初始化。

【讨论】:

    【解决方案2】:

    为什么这行不通?

    由于对象的构造方式,此设计将不起作用。

    当您构造Derived 时,首先发生的是Base 对象是使用Base 构造函数构造的。此时没有Derived 对象,因此如果您在Base 构造函数中调用虚函数,那么它将是对Base 类有效的虚函数,直到您离开Base 构造函数身体。

    这是标准允许的,但有限制:

    [base.class.init]/16:可以为正在构建的对象调用成员函数(包括虚成员函数)。 (...) 然而, 如果这些操作在 ctor-initializer 中执行(或在 从 ctor-initializer 直接或间接调用的函数)之前 基类的所有 mem-initializers 都已完成,程序 具有未定义的行为。

    这些限制不适用于从构造函数主体调用的虚函数,因为主体在所有初始化程序之后执行。

    但在您的情况下,Base 的虚函数是纯虚函数。所以根据以下条款它是UB:

    [class.abstract]/6:可以从抽象类的构造函数(或析构函数)调用成员函数;制作的效果 直接对纯虚函数的虚调用或 间接地用于从这样的对象创建(或销毁)的对象 构造函数(或析构函数)未定义

    有什么替代方法

    不幸的是,除了使用two step initialization 之外,没有其他真正的替代方法,您首先构造对象,然后在使用对象之前调用初始化函数。

    这种方法的唯一问题是忘记调用初始化函数的风险。你可以保护你免受这种情况:

    • 使用一个标志来指示初始化是否已经发生,并在所有成员函数中检查这个标志(是的,这有点开销)。
    • 如果您有一个抽象类,您可能会使用工厂模式(工厂方法或抽象工厂)。然后,您可以让工厂接听电话。
    • 您可以使用构建器模式,并确保构造器只对不会忘记初始化的构建器可见。

    您的代码的其他问题

    您必须注意如何从派生构造函数“调用”基构造函数:

    class Derived : Base {
    public:
        Derived() : Base(42) // this is the correct place !
        {  
        //Base::Base(42);  //<====  OUCH !!! NO !! This creates a temporary base object !!
        }
        ...
    };
    

    你还需要小心纯虚拟(我不知道这是一个错字还是你的编译器可以编译你的代码):

    virtual void someMethod() = 0;  // if it's abstract, no pending {} !
    

    【讨论】:

      猜你喜欢
      • 2018-11-29
      • 1970-01-01
      • 2015-08-06
      • 1970-01-01
      • 2021-06-24
      • 1970-01-01
      • 1970-01-01
      • 2014-04-30
      • 2016-03-12
      相关资源
      最近更新 更多