将继承更改为虚拟的后果?
Consequences of changing inheritance to virtual?
我正在做一个我没有开始的大项目。我的任务是为已有的功能添加一些额外的功能。我处于必须使用虚拟继承的情况,因为我有一个钻石模型。情况如下图所示:
Base class
/ \
/ \
My new class A class that was there before (OldClass)
\ /
\ /
\ /
\ /
My other new class
为了实现这一点,中间 have to 中的 类 都通过 public virtual
从基础继承,而不仅仅是 public
。所以:
class OldClass: public BaseClass {}
必须变成:
class OldClass: public virtual BaseClass {}
由于这个项目确实很大,而我只处理其中的一小部分,所以我不想因为这样做而破坏它。我的临时测试有效,程序似乎运行良好,但我仍然持怀疑态度。
所以我的问题是: 添加 virtual
关键字会产生什么副作用和后果?有什么需要担心的吗?
根据标准,虚拟继承与非虚拟继承完全相同,只是派生对象中只有一个虚拟继承的实例class。
所以在原始代码中没有任何东西对从 Base
派生的 classes 有多重继承,将 Base
的继承更改为虚拟应该不会改变行为。但是您必须咨询或构建 class 层次结构才能确保这一点。
来自 n4096 草案的参考文献:
10.1 Multiple base classes [class.mi]
...
4 A base class specifier that does not contain the keyword virtual, specifies a non-virtual base class. A base
class specifier that contains the keyword virtual, specifies a virtual base class. For each distinct occurrence
of a non-virtual base class in the class lattice of the most derived class, the most derived object (1.8) shall
contain a corresponding distinct base class subobject of that type. For each distinct base class that is
specified virtual, the most derived object shall contain a single base class subobject of that type.
除了该段后面的示例,我在 [class.mi] 中找不到其他关于虚拟继承的参考。
这很难以这种抽象的方式回答,因为这完全取决于 classes 在做什么以及您如何使用它们。
拥有虚拟继承意味着您的两个中间 class 将共享相同的 Base
。那是你想要的吗?
没有语言规则禁止在层次结构中实际有两个单独的 Base
classes。只是调用成员函数有点困难,因为你必须通过在函数名称前加上前缀 p->NewClass::base_function()
或 p->OldClass::base_function();
来明确指示你要调用哪个副本。如果共享 Base
数据不是您所需要的,那是可行的。
就像 Serge 说的,如果其他 class 只继承了一个 Base
,它仍然只包含一个 Base。
除了 所说的关于调用构造函数之外,使用虚拟继承意味着您应该更小心地进行强制转换:您需要在向下转换指针时使用 dynamic_cast<>
包含虚拟继承的层次结构,因为基础对象和转换的目标类型之间的相对偏移取决于对象的具体实际类型。除此之外,其他一切都应该按预期工作。
直接结果是,对于常规继承,派生 classes 调用直接基类的构造函数,而对于虚拟继承,最派生的 class (即直接实例化的那个)确实如此,因为这是唯一知道所有虚拟基地的地方。
比较:
struct A { A(int) { } };
struct B : A { B(int i) : A(i) { } };
struct C : B { C(int i) : B(i) { } };
对
struct A { A(int) { } };
struct B : virtual A { B(int i) : A(i) { } };
// wrong: struct C : B { C(int i) : B(i) { } };
struct C : B { C(int i) : A(i), B(i) { } }; // correct
此外,初始化器行为不同,因为如果 B
不是最派生的 class:[=19,B
中 A
的初始化器将被忽略=]
struct A { A(int i) { cout << 'A' << i; } };
struct B : virtual A { B(int i) : A(i+1) { cout << 'B' << i; } };
struct C : B { C(int i) : A(i+1), B(i+1) { cout << 'C' << i; } };
A a(0); // prints A0
B b(0); // prints A1B0
C c(0); // prints A1B1C0
如果你在这里有非虚拟继承(这将迫使你删除 C
构造函数中的 A
初始化程序,第三行将输出 A2B1C0
.
我正在做一个我没有开始的大项目。我的任务是为已有的功能添加一些额外的功能。我处于必须使用虚拟继承的情况,因为我有一个钻石模型。情况如下图所示:
Base class
/ \
/ \
My new class A class that was there before (OldClass)
\ /
\ /
\ /
\ /
My other new class
为了实现这一点,中间 have to 中的 类 都通过 public virtual
从基础继承,而不仅仅是 public
。所以:
class OldClass: public BaseClass {}
必须变成:
class OldClass: public virtual BaseClass {}
由于这个项目确实很大,而我只处理其中的一小部分,所以我不想因为这样做而破坏它。我的临时测试有效,程序似乎运行良好,但我仍然持怀疑态度。
所以我的问题是: 添加 virtual
关键字会产生什么副作用和后果?有什么需要担心的吗?
根据标准,虚拟继承与非虚拟继承完全相同,只是派生对象中只有一个虚拟继承的实例class。
所以在原始代码中没有任何东西对从 Base
派生的 classes 有多重继承,将 Base
的继承更改为虚拟应该不会改变行为。但是您必须咨询或构建 class 层次结构才能确保这一点。
来自 n4096 草案的参考文献:
10.1 Multiple base classes [class.mi]
...
4 A base class specifier that does not contain the keyword virtual, specifies a non-virtual base class. A base class specifier that contains the keyword virtual, specifies a virtual base class. For each distinct occurrence of a non-virtual base class in the class lattice of the most derived class, the most derived object (1.8) shall contain a corresponding distinct base class subobject of that type. For each distinct base class that is specified virtual, the most derived object shall contain a single base class subobject of that type.
除了该段后面的示例,我在 [class.mi] 中找不到其他关于虚拟继承的参考。
这很难以这种抽象的方式回答,因为这完全取决于 classes 在做什么以及您如何使用它们。
拥有虚拟继承意味着您的两个中间 class 将共享相同的 Base
。那是你想要的吗?
没有语言规则禁止在层次结构中实际有两个单独的 Base
classes。只是调用成员函数有点困难,因为你必须通过在函数名称前加上前缀 p->NewClass::base_function()
或 p->OldClass::base_function();
来明确指示你要调用哪个副本。如果共享 Base
数据不是您所需要的,那是可行的。
就像 Serge 说的,如果其他 class 只继承了一个 Base
,它仍然只包含一个 Base。
除了 dynamic_cast<>
包含虚拟继承的层次结构,因为基础对象和转换的目标类型之间的相对偏移取决于对象的具体实际类型。除此之外,其他一切都应该按预期工作。
直接结果是,对于常规继承,派生 classes 调用直接基类的构造函数,而对于虚拟继承,最派生的 class (即直接实例化的那个)确实如此,因为这是唯一知道所有虚拟基地的地方。
比较:
struct A { A(int) { } };
struct B : A { B(int i) : A(i) { } };
struct C : B { C(int i) : B(i) { } };
对
struct A { A(int) { } };
struct B : virtual A { B(int i) : A(i) { } };
// wrong: struct C : B { C(int i) : B(i) { } };
struct C : B { C(int i) : A(i), B(i) { } }; // correct
此外,初始化器行为不同,因为如果 B
不是最派生的 class:[=19,B
中 A
的初始化器将被忽略=]
struct A { A(int i) { cout << 'A' << i; } };
struct B : virtual A { B(int i) : A(i+1) { cout << 'B' << i; } };
struct C : B { C(int i) : A(i+1), B(i+1) { cout << 'C' << i; } };
A a(0); // prints A0
B b(0); // prints A1B0
C c(0); // prints A1B1C0
如果你在这里有非虚拟继承(这将迫使你删除 C
构造函数中的 A
初始化程序,第三行将输出 A2B1C0
.