我有一个具有钻石结构的类层次结构,并且有一个没有默认构造函数的基类,因为它具有引用成员。
代码如下:
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”编译器。)
对于基类中的虚拟继承和引用的组合,还有其他解决方案吗?
答案 0 :(得分:3)
您没有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 ) {}
};
答案 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。