C++多重继承的一些基础知识
some basics about c++ multiple-inheritance
在我的 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() {}
};
现在我想定义一个新的 class Obj2
,它是 Obj1
并且是(实现)AbstractB
。基本上
class Obj2 : public Obj1, public AbstractB {
void Doh();
};
此处出现编译错误
经过一些考虑,我怀疑我必须在 Obj2
中(重新)定义 Foo()
、Bar()
和 Woo()
,因为编译器不知道正确的路径按照以下步骤解决它们(基本上是从 Obj1
或 AbstractB
传递过来的?)。我对这一点是否正确?
如果是这样,并且由于我解析 Foo()
、Bar()
和 Woo()
的正确路径总是从 ObjA
传递,是否有任何语法可以避免父 class 调用每个方法?也就是说我能不能比
更简洁
class Obj2 : public Obj1, public AbstractB {
void Doh();
void Foo() { A::Foo() }
void Bar() { A::Bar() }
void Woo() { A::Woo() }
}
您可以使用virtual
class Obj2 : public Obj1, public virtual AbstractB {
void Doh();
};
当使用 = 0
创建纯虚方法时,您还需要使用 virtual
关键字,例如
class AbstractA
{
virtual void Foo() = 0;
virtual void Bar() = 0;
virtual void Woo() = 0;
};
此外,您可能还想制作这些功能public。
另一个非常重要的事情是 always 在 classes 中声明一个 virtual destructor designed to be inheritance, see example: When to use virtual destructors?.
class Obj1
实现了接口 AbstractA
,因此是一个具体的 class。 AbstractB
使用方法 Doh
扩展接口 AbstractA
。
但是,当您继承 Obj1
(继承 AbstractA
)和 AbstractB
(也继承 AbstractA
)时,这会导致问题。因此,您继承了 AbstractA
'twice',除非您使用 虚拟继承 ,否则它不起作用,请参阅:https://en.wikipedia.org/wiki/Virtual_inheritance.
更简单的方法是不让 AbstractB
继承自 AbstractA
。
因此,声明 classes 的一个好方法是:
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() */
};
您正在尝试解决现在在 C++ 社区中臭名昭著的问题 "Diamond of Death Problem"。 C++ 的创造者 Bjarne Stroustroup 给出了这个问题的 classic 说明。
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();
}
查看上面的 class 层次结构。正如您猜对的那样,编译器不知道应该将哪个版本的 Method1() 和 Method2() 纳入 class 具体的定义中。由于 Base1 和 Base2 都有自己的 Method1() 和 Method2() 版本,编译器有 2 个选项来选择这些定义,它只是变得困惑并开始抛出所有这些错误,并在周围抛出单词 "ambiguous"地方。
Bjarne Stroustroup 针对此问题提出的解决方案是一个名为 "Virtual Inheritence" 的诡计。实际上,当您从 class "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 具体:public Base1,public Base2
{
}
"Concrete" 只获得 "Base" 的一个副本,"Base1" 和 "Base2" 都指向它,从而解决了编译器的歧义。
编译器如何实现这一点?每个带有虚方法的 class 都与称为虚函数 table 或 vtable 的东西相关联。 vtable 有一个指向 class 中所有虚拟方法的指针。当加载 classes Base1 和 Base2 时,这两个 classes 的 vtables 持有一个指向 Base class 的指针。同理,加载Concrete时,Concrete的vtable也指向Base的同一个单例,有效保证了Base的单例。
这里实例化Concrete时需要注意的地方。您必须显式调用 Base 的构造函数,就像显式调用 Base1 和 Base2 的构造函数一样。像
Concrete() : Base(), Base1(), Base2()
{
}
另外,如果在Base1和Base2的构造函数中显式调用了Base()的构造函数,则实例化Concrete时会跳过,直接调用Base的构造函数。
在我的 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() {}
};
现在我想定义一个新的 class Obj2
,它是 Obj1
并且是(实现)AbstractB
。基本上
class Obj2 : public Obj1, public AbstractB {
void Doh();
};
此处出现编译错误
经过一些考虑,我怀疑我必须在 Obj2
中(重新)定义 Foo()
、Bar()
和 Woo()
,因为编译器不知道正确的路径按照以下步骤解决它们(基本上是从 Obj1
或 AbstractB
传递过来的?)。我对这一点是否正确?
如果是这样,并且由于我解析 Foo()
、Bar()
和 Woo()
的正确路径总是从 ObjA
传递,是否有任何语法可以避免父 class 调用每个方法?也就是说我能不能比
class Obj2 : public Obj1, public AbstractB {
void Doh();
void Foo() { A::Foo() }
void Bar() { A::Bar() }
void Woo() { A::Woo() }
}
您可以使用virtual
class Obj2 : public Obj1, public virtual AbstractB {
void Doh();
};
当使用 = 0
创建纯虚方法时,您还需要使用 virtual
关键字,例如
class AbstractA
{
virtual void Foo() = 0;
virtual void Bar() = 0;
virtual void Woo() = 0;
};
此外,您可能还想制作这些功能public。 另一个非常重要的事情是 always 在 classes 中声明一个 virtual destructor designed to be inheritance, see example: When to use virtual destructors?.
class Obj1
实现了接口 AbstractA
,因此是一个具体的 class。 AbstractB
使用方法 Doh
扩展接口 AbstractA
。
但是,当您继承 Obj1
(继承 AbstractA
)和 AbstractB
(也继承 AbstractA
)时,这会导致问题。因此,您继承了 AbstractA
'twice',除非您使用 虚拟继承 ,否则它不起作用,请参阅:https://en.wikipedia.org/wiki/Virtual_inheritance.
更简单的方法是不让 AbstractB
继承自 AbstractA
。
因此,声明 classes 的一个好方法是:
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() */
};
您正在尝试解决现在在 C++ 社区中臭名昭著的问题 "Diamond of Death Problem"。 C++ 的创造者 Bjarne Stroustroup 给出了这个问题的 classic 说明。
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();
}
查看上面的 class 层次结构。正如您猜对的那样,编译器不知道应该将哪个版本的 Method1() 和 Method2() 纳入 class 具体的定义中。由于 Base1 和 Base2 都有自己的 Method1() 和 Method2() 版本,编译器有 2 个选项来选择这些定义,它只是变得困惑并开始抛出所有这些错误,并在周围抛出单词 "ambiguous"地方。
Bjarne Stroustroup 针对此问题提出的解决方案是一个名为 "Virtual Inheritence" 的诡计。实际上,当您从 class "Base".
派生时,您要做的就是引入关键字 virtualclass 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 具体:public Base1,public Base2 { }
"Concrete" 只获得 "Base" 的一个副本,"Base1" 和 "Base2" 都指向它,从而解决了编译器的歧义。 编译器如何实现这一点?每个带有虚方法的 class 都与称为虚函数 table 或 vtable 的东西相关联。 vtable 有一个指向 class 中所有虚拟方法的指针。当加载 classes Base1 和 Base2 时,这两个 classes 的 vtables 持有一个指向 Base class 的指针。同理,加载Concrete时,Concrete的vtable也指向Base的同一个单例,有效保证了Base的单例。
这里实例化Concrete时需要注意的地方。您必须显式调用 Base 的构造函数,就像显式调用 Base1 和 Base2 的构造函数一样。像
Concrete() : Base(), Base1(), Base2()
{
}
另外,如果在Base1和Base2的构造函数中显式调用了Base()的构造函数,则实例化Concrete时会跳过,直接调用Base的构造函数。