在我的C ++中,我有类似下面的内容
class AbstractA {
void Foo() = 0;
void Bar() = 0;
void Woo() = 0;
};
class AbstractB : public AbstractA {
void Doh() = 0;
};
class Obj1 : public AbstractA {
void Foo() {}
void Bar() {}
void Woo() {}
};
现在我想要定义一个Obj2
的新类Obj1
,它是(实现)AbstractB
。基本上
class Obj2 : public Obj1, public AbstractB {
void Doh();
};
此处发生编译错误。
经过一些考虑后,我怀疑我必须(重新)在Foo()
内定义Bar()
,Woo()
和Obj2
,因为编译器不知道要遵循的正确路径解决它们(基本上从Obj1
或AbstractB
传递?)。我对这一点是否正确?
如果是这样,并且由于我正确的解决Foo()
,Bar()
和Woo()
的路径总是从ObjA
传递,是否有任何语法可以避免父类调用每种方法?换句话说,我可以比
class Obj2 : public Obj1, public AbstractB {
void Doh();
void Foo() { A::Foo() }
void Bar() { A::Bar() }
void Woo() { A::Woo() }
}
答案 0 :(得分:2)
您可以使用virtual
class Obj2 : public Obj1, public virtual AbstractB {
void Doh();
};
答案 1 :(得分:2)
你正试图解决现在已经成为C ++社区臭名昭着的可怕的死亡问题"死亡问题"。 C ++的创建者Bjarne Stroustroup给出了这个问题的经典例证。
class Base {
public:
virtual void Method1() = 0;
virtual void Method2() = 0;
};
class Base1 : public Base
{
public:
virtual void Method1();
virtual void Method2();
};
class Base2 : public Base
{
public:
virtual void Method1();
virtual void Method2();
}
class Concrete : public Base1, public Base2
{
virtual void Method1();
virtual void Method2();
}
查看上面的类层次结构。正如您正确猜到的那样,编译器不知道Method1()和Method2()的哪个版本应该被用于Concrete类的定义中。由于Base1和Base2都有自己的Method1()和Method2()版本,因此编译器有2个选项来选择这些定义,它只会让人感到困惑并开始抛出所有这些错误,并使用" ambiguous"扔到处都是。
Bjarne Stroustroup为这个问题提出的解决方案是一个叫做“虚拟继承”的诡计。实际上,您需要做的是在从类" Base"派生时引入关键字virtual。
class Base1 : virtual public Base
{
public:
virtual void Method1();
virtual void Method2();
};
class Base2 : virtual public Base
{
public:
virtual void Method1();
virtual void Method2();
}
这告诉编译器,尽管Base1和Base2中有多个Base副本,但它们实际上应该指向相同版本的Base。 这可确保您在定义时: class Concrete:public Base1,public Base2 { }
"混凝土"只获得一个" Base"的副本,这两个副本都是" Base1"和" Base2"指向,从而解决了编译器的歧义。 编译器如何实现这一目标?每个具有虚方法的类都与称为虚函数表或vtable的东西相关联。 vtable有一个指向该类中所有虚拟方法的指针。当加载类Base1和Base2时,这两个类的vtable包含一个指向Base类的指针。类似地,当加载Concrete时,Concrete的vtable也指向同一个Base实例,有效地确保了Base的单个实例。
在实例化Concrete时,需要注意一点。您将必须显式调用Base的构造函数,就像您将显式调用Base1和Base2的构造函数一样。像
这样的东西Concrete() : Base(), Base1(), Base2()
{
}
此外,如果在Base1和Base2的构造函数中显式调用Base()的构造函数,则在实例化Concrete时将跳过此操作,并且将直接调用Base的构造函数。
答案 2 :(得分:1)
使用= 0
创建纯虚拟方法时,您还需要使用virtual
关键字,例如
class AbstractA
{
virtual void Foo() = 0;
virtual void Bar() = 0;
virtual void Woo() = 0;
};
此外,您可能希望公开这些功能。 另一个非常重要的事情是始终在设计用于继承的类中声明虚拟析构函数,例如参见:When to use virtual destructors?。
类Obj1
实现了接口AbstractA
,因此是一个具体的类。 AbstractB
使用方法AbstractA
扩展了接口Doh
。
但是,当您继承Obj1
(继承AbstractA
)和AbstractB
(也继承AbstractA
)时,这会导致问题。因此,您继承AbstractA
'两次',除非您使用虚拟继承,否则无效,请参阅:https://en.wikipedia.org/wiki/Virtual_inheritance。
更简单的方法是不让AbstractB
继承AbstractA
。
因此,宣布您的课程的好方法是:
class AbstractA
{
public:
virtual ~AbstractA() {}
virtual void Foo() = 0;
virtual void Bar() = 0;
virtual void Woo() = 0;
};
class AbstractB
{
public:
virtual ~AbstractB() {}
virtual void Doh() = 0;
};
/* This is now a concrete class that implements the 'interface' AbstractA. */
class Obj1 : public AbstractA
{
public:
void Foo() {} /* Implements AbstractA::Foo() */
void Bar() {} /* Implements AbstractA::Bar() */
void Woo() {} /* Implements AbstractA::Woo() */
};
/* Inherits Obj1's implementations of AbstractA and implements AbstractB */
class Obj2 : public Obj1, public AbstractB
{
public:
void Doh() {} /* Implements AbstractB::Woo() */
};