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

(Why) Is virtual base class constructor call required in pure virtual derived class?

本文关键字:虚拟 基类 函数调用 为什么 派生 是否      更新时间:2023-10-16

我有一个带有菱形结构的类层次结构和一个没有默认构造函数的基类,因为它有引用成员。

代码如下所示:

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

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

请注意,是纯虚类,即在 no 他们的构造函数会调用Base的构造函数的情况。 这是由于虚拟继承,这意味着派生最多的类Bottom调用Base的构造函数。

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

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

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

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

我会说你有一个很大的设计问题,因为obj是由Right初始化的,所以后者应该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 ) {}
};

撇开设计问题(这里显然存在)不谈,我完全同意你的推理,因为Left是一个抽象类,所以Left构造函数都不必调用任何Base构造函数,因此需要它很奇怪。

事实上,我用几个编译器版本测试了你的代码,它确实在 gcc 7.1 及更高版本、clang 3.4.1 及更高版本以及所有可用的 msvc 版本中编译得很好。

所以,我的假设是,这只是早期编译器版本中的一个错误。谁能证实这一点?

另请注意,如果将virtual void leftsMethod() = 0;更改为virtual void leftsMethod() {},以便Left不再抽象,则即使使用最新版本,错误也会返回。这是完全有道理的,因为现在你可以实例化一个Left实例,因此Left的构造函数可以调用Base的构造函数之一。

可能的解决方法

如果您无法切换到较新的编译器,并且无法更改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实际上应该有一个虚拟析构函数!我不知道你是否只是为了简洁而没有把它包括在这里,或者它真的没有。如果没有,那么在其上构建类层次结构是一个非常糟糕的主意。

官方文档中的引用可能会描述这种情况:

实际上,这意味着在创建具有虚拟基类的具体类

时,必须准备好传递调用虚拟基类的构造函数所需的任何参数。当然,如果您的类祖先中存在多个虚拟基类,则必须准备好调用它们的所有构造函数。这可能意味着派生最多的类的构造函数需要的参数比您想象的要多。

请参阅此常见问题解答。