以下代码:
struct interface_base
{
virtual void foo() = 0;
};
struct interface : public interface_base
{
virtual void bar() = 0;
};
struct implementation_base : public interface_base
{
void foo();
};
struct implementation : public implementation_base, public interface
{
void bar();
};
int main()
{
implementation x;
}
无法编译并出现以下错误:
test.cpp: In function 'int main()':
test.cpp:23:20: error: cannot declare variable 'x' to be of abstract type 'implementation'
test.cpp:16:8: note: because the following virtual functions are pure within 'implementation':
test.cpp:3:18: note: virtual void interface_base::foo()
我玩过它并想出制作'界面 - > interface_base'和'implementation_base - > interface_base'继承虚拟,修复问题,但我不明白为什么。有人可以解释一下发生了什么吗?
P.S。我故意省略了虚拟析构函数以缩短代码。请不要告诉我把它们放进去,我已经知道了。)
答案 0 :(得分:14)
您的继承树中有两个 interface_base
基类。这意味着您必须提供foo()
的两个实现。调用它们中的任何一个都会非常尴尬,需要多个强制转换才能消除歧义。这通常不是你想要的。
要解决此问题,请使用虚拟继承:
struct interface_base
{
virtual void foo() = 0;
};
struct interface : virtual public interface_base
{
virtual void bar() = 0;
};
struct implementation_base : virtual public interface_base
{
void foo();
};
struct implementation : public implementation_base, virtual public interface
{
void bar();
};
int main()
{
implementation x;
}
使用虚拟继承,在继承层次结构中只为所有虚拟提及创建了一个有问题的基类实例。因此,只有一个foo()
,implementation_base::foo()
可以满足。{/ p>
有关详细信息,see this prior question - 答案提供了一些很好的图表,使这一点更加清晰。
答案 1 :(得分:11)
通常的C ++习语是:
在这种情况下,我们会:
struct interface_base
{
virtual void foo() = 0;
};
struct interface : virtual public interface_base
{
virtual void bar() = 0;
};
struct implementation_base : virtual public interface_base
{
void foo();
};
struct implementation : private implementation_base,
virtual public interface
{
void bar();
};
在implementation
中,唯一的interface_base
虚拟基数为:
interface
公开继承:implementation
--public - > interface
- 公共 - > interface_base
implementation_base
私下继承:implementation
- 私人 - > implementation_base
- 公共 - > interface_base
当客户端代码执行其中一个派生到基本转换时:
重要的是,从派生类到给定的基类子对象,只有一个可访问的继承路径;其他不可访问的路径被简单地忽略。因为基类的继承只是虚拟的,所以只有一个基类主题,所以这些转换永远不会有歧义。
此处,implementation
到interface_base
的转换始终可以通过客户端代码interface
完成;另一条无法进入的道路根本不重要。 唯一的interface_base
虚拟基地是公开继承自implementation
。
在许多情况下,实现类(implementation
,implementation_base
)将对客户端代码保持隐藏:只有指针或对接口类的引用(interface
,{{1} })将被暴露。
答案 2 :(得分:2)
对于“解决”钻石继承问题的情况,bdonlan提供的解决方案是有效的。话虽如此,你可以避免设计中的钻石问题。为什么必须将给定类的每个实例视为两个类?你是否会将这个相同的对象传递给类似于以下内容的类:
void ConsumeFood(Food *food);
void ConsumeDrink(Drink *drink);
class NutritionalConsumable {
float calories() = 0;
float GetNutritionalValue(NUTRITION_ID nutrition) = 0;
};
class Drink : public NutritionalConsumable {
void Sip() = 0;
};
class Food : public NutritionalConsumable {
void Chew() = 0;
};
class Icecream : public Drink, virtual public Food {};
void ConsumeNutrition(NutritionalConsumable *consumable) {
ConsumeFood(dynamic_cast<Food*>(food));
ConsumeDrink(dynamic_cast<Drink*>(drink));
}
// Or moreso
void ConsumeIcecream(Icecream *icecream) {
ConsumeDrink(icecream);
ConsumeFood(icecream);
}
在这种情况下,Icecream
只需实施NutritionalConsumable
并提供将返回代理的GetAsDrink()
和GetAsFood()
方法,这纯粹是为了更好作为食物或饮料出现。否则,这表明有一个方法或对象接受Food
,但不知何故希望稍后将其视为Drink
,这只能通过dynamic_cast
来实现,并且不需要是一个更合适的设计。