(为什么)在纯虚拟派生类中是否需要虚拟基类构造函数调用?

时间:2019-03-22 10:23:02

标签: c++ visual-studio-2010 pure-virtual virtual-inheritance

我有一个具有钻石结构的类层次结构,并且有一个没有默认构造函数的基类,因为它具有引用成员。

代码如下:

class Base
{
public:
    Base( CustomType& obj ) : refObj_( obj ) {}
    virtual ~Base() {}
private:
    CustomType& refObj_;
};

class Left : public virtual Base
{
public:
    Left() {}; // ERROR: Compiler requests calling Base's constructor
    virtual void leftsMethod() = 0;
};

class Right : public virtual Base
{
public:
    Right( CustomType& obj ) : Base( obj ) {}; // Compiles, but Base( obj ) never gets called here
    virtual void rightsMethod() = 0;
};

class Bottom : public Left, public Right
{
public:
    Bottom( CustomType& obj ) : Base( obj ), Left(), Right( obj ) {}
};

编辑:添加了虚拟基础析构函数(原始代码已提供)

请注意,是纯虚拟类,即 情况下,其构造函数将调用 Base 的构造函数。 这是由于虚拟继承,这意味着最派生的类 Bottom 调用了 Base 的构造函数。

我的问题:为什么我仍然必须在 Left 的初始化列表中调用 Base 的构造函数?我想避免这种情况,并将引用直接从 Bottom 的构造方法传递给 Base 。 (我正在使用MSVC 2010的“半C ++ 11”编译器。)

对于基类中的虚拟继承和引用的组合,还有其他解决方案吗?

3 个答案:

答案 0 :(得分:3)

您没有Base的默认构造函数,但是在创建Left对象(不是Left的对象时,Bottom仍需要初始化一个构造函数。一个)。

问题是您在Base中有一个引用,这意味着您可能没有简单的构造函数。

我会说您有一个很大的设计问题,因为objRight初始化,所以后者应保留obj,而不是Base

Base的虚拟析构函数也在哪里?

class Base
{
public:
    Base() = default;
    ~Base() {}
};

class Left : public virtual Base
{
public:
    virtual void leftsMethod();
};

class Right : public virtual Base
{
    int& refObj_;
public:
    Right( int& obj ) : refObj_( obj ) {}
    virtual void rightsMethod();
};

class Bottom : public Left, public Right
{
public:
    Bottom( int& obj ) : Right( obj ) {}
};

答案 1 :(得分:2)

除了设计问题(这里显然存在)之外,我完全同意您的推理,因为Left是抽象类,因此Left构造函数将不必调用任何Base构造函数,因此很奇怪。

事实上,我使用多种编译器版本测试了您的代码,并且它确实可以编译,并且可以使用gcc 7.1及更高版本,clang 3.4.1及其更高版本以及所有可用的{ {3}}版本。

因此,我的假设是,这只是早期编译器版本中的错误。有人可以确认吗?

还请注意,如果将virtual void leftsMethod() = 0;更改为virtual void leftsMethod() {},则Left不再是抽象的,即使使用msvc,也会返回错误。这非常有道理,因为现在您可以实例化一个Left实例,因此由Left的构造函数来调用Base'中的一个s的构造函数。

可能的解决方法

如果您无法切换到较新的编译器,并且无法更改Base的实现,那么这可能是您的可行解决方案:

在某处定义CustomType的虚拟实例。然后,您可以提供“必需的”默认构造函数,如下所示:

class CustomType{};

class Base
{
public:
    Base( CustomType& obj ) : refObj_( obj ) {}
private:
    CustomType& refObj_;
};

static CustomType dummy;

class Left : public virtual Base
{
protected:
    Left() : Base(dummy) {}; // note here
public:
    virtual void leftsMethod() = 0;
};

class Right : public virtual Base
{
protected:
    Right() : Base(dummy) {}; // and here
public:
    virtual void rightsMethod() = 0;
};

class Bottom : public Left, public Right
{
public:
    Bottom( CustomType& obj ) : Base( obj ), Left(), Right() {}
    virtual void leftsMethod() override {}
    virtual void rightsMethod() override {}
};

void test()
{
    CustomType c;
    Bottom b(c);
}

这只是为了使编译器满意,您关于这些构造函数永远不会调用Base(dummy)的观点仍然成立。

但是请注意,Base应该确实具有虚拟析构函数!我不知道您是否只是出于简洁目的不将其包括在此处,或者它是否真的没有。如果不是这样,在它之上建立一个类层次结构是一个非常糟糕的主意。

答案 2 :(得分:0)

官方文档中的一句话可能描述了这种情况:

  

实际上,这意味着当您创建具有虚拟基类的具体类时,必须准备好传递调用虚拟基类的构造函数所需的任何参数。而且,当然,如果类祖先中的任何地方都有多个虚拟基类,则必须准备调用它们的所有构造函数。这可能意味着派生最多的类的构造函数需要的参数比您想象的要多。

请参阅此FAQ