Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/139.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
“在哪里?”;虚拟的;在复杂的多重继承层次结构中是否需要关键字? 我理解C++虚拟继承的基础知识。但是,我不知道在复杂的类层次结构中到底需要在哪里使用virtual关键字。例如,假设我有以下类: A / \ B C / \ / \ D E F \ / \ / G H \ / I_C++_Multiple Inheritance_Virtual Inheritance - Fatal编程技术网

“在哪里?”;虚拟的;在复杂的多重继承层次结构中是否需要关键字? 我理解C++虚拟继承的基础知识。但是,我不知道在复杂的类层次结构中到底需要在哪里使用virtual关键字。例如,假设我有以下类: A / \ B C / \ / \ D E F \ / \ / G H \ / I

“在哪里?”;虚拟的;在复杂的多重继承层次结构中是否需要关键字? 我理解C++虚拟继承的基础知识。但是,我不知道在复杂的类层次结构中到底需要在哪里使用virtual关键字。例如,假设我有以下类: A / \ B C / \ / \ D E F \ / \ / G H \ / I,c++,multiple-inheritance,virtual-inheritance,C++,Multiple Inheritance,Virtual Inheritance,如果我想确保这些类在任何子类中都不会出现一次以上,那么哪些基类需要标记为virtual?都是吗?或者,仅在直接派生自可能具有多个实例的类(即B、C、D、e和F;以及G和H(但仅用于基类e,而不用于基类D和F))的类上使用它就足够了吗?我个人的建议是从B和C开始:虚拟a,然后继续添加,直到编译器停止抱怨 实际上,我会说B和C:virtuala,G和H:virtuale,E:virtualb和C。所有其他继承链接都可以是普通继承。但是,这个怪物需要60年才能进行虚拟调用。从a、B、C和E类(位于菱形

如果我想确保这些类在任何子类中都不会出现一次以上,那么哪些基类需要标记为
virtual
?都是吗?或者,仅在直接派生自可能具有多个实例的类(即B、C、D、e和F;以及G和H(但仅用于基类e,而不用于基类D和F))的类上使用它就足够了吗?

我个人的建议是从B和C开始:虚拟a,然后继续添加,直到编译器停止抱怨


实际上,我会说B和C:virtuala,G和H:virtuale,E:virtualb和C。所有其他继承链接都可以是普通继承。但是,这个怪物需要60年才能进行虚拟调用。

从a、B、C和E类(位于菱形顶部)继承时,必须指定
virtual
继承


如果您希望每个类型的每个实例(只有一个A、只有一个B等)只有一个每个类型的“物理”实例,那么每次使用继承时只需使用虚拟继承即可


如果需要其中一种类型的单独实例,请使用普通继承

我一起玩了一个程序,可以帮助你研究虚拟基地的复杂性。它将
I
下的类层次结构打印为适合graphiviz()的有向图。每个实例都有一个计数器,可以帮助您理解构造顺序。以下是节目:

#include <stdio.h>
int counter=0; 



#define CONN2(N,X,Y)\
    int id; N() { id=counter++; }\
    void conn() \
    {\
        printf("%s_%d->%s_%d\n",#N,this->id,#X,((X*)this)->id); \
        printf("%s_%d->%s_%d\n",#N,this->id,#Y,((Y*)this)->id); \
        X::conn(); \
        Y::conn();\
    }
#define CONN1(N,X)\
    int id; N() { id=counter++; }\
    void conn() \
    {\
        printf("%s_%d->%s_%d\n",#N,this->id,#X,((X*)this)->id); \
        X::conn(); \
    }

struct A { int id; A() { id=counter++; } void conn() {} };
struct B : A { CONN1(B,A) };
struct C : A { CONN1(C,A)  };
struct D : B { CONN1(D,B) };
struct E : B,C { CONN2(E,B,C) };
struct F : C { CONN1(F,C) };
struct G : D,E { CONN2(G,D,E) };
struct H : E,F { CONN2(H,E,F) };
struct I : G,H { CONN2(I,G,H) };
int main()
{
    printf("digraph inh {\n");
    I i; 
    i.conn(); 
    printf("}\n");
}
…结果为菱形(查看数字以了解构造顺序!!)

但如果你把所有的基地都虚拟化:

struct A { int id; A() { id=counter++; } void conn() {} };
struct B : virtual A { CONN1(B,A) };
struct C : virtual A { CONN1(C,A)  };
struct D : virtual B { CONN1(D,B) };
struct E : virtual B, virtual C { CONN2(E,B,C) };
struct F : virtual C { CONN1(F,C) };
struct G : virtual D, virtual E { CONN2(G,D,E) };
struct H : virtual E, virtual F { CONN2(H,E,F) };
struct I : virtual G,virtual H { CONN2(I,G,H) };
您将得到一个具有不同初始化顺序的菱形:


玩得开心

如果您想确保层次结构中顶级类的对象(在您的例子中是
I
)包含每个父类的一个子对象,那么必须查找层次结构中具有多个超类的所有类,并使这些类成为其超类的虚拟基。就这样

在您的情况下,
A
B
C
E
类必须在每次您在此层次结构中从它们继承时成为虚拟基类


D
F
G
H
不必成为虚拟基类。

编辑的:我认为A是最派生的类;)

@路德的回答真的很酷,但回到原来的问题:

当从继承层次结构中至少有一个其他类继承的任何类继承时,您需要使用
virtual
继承(在路德的图表中,它意味着至少有两个箭头指向该类)

在这里,在
D
F
G
H
之前没有必要这样做,因为只有一个类是从它们派生的(目前没有一个类是从
I
派生的)

但是,如果您事先不知道另一个类是否会从基类继承,可以添加
virtual
作为预防措施。例如,建议一个
Exception
类从
std::Exception
虚拟继承,而不是由Stroustrup自己继承


正如卢瑟所指出的,它修改了实例化顺序(并且对性能有轻微的影响),但我认为任何依赖于施工顺序的设计都是错误的。正如一个精确性:您仍然有保证基类在派生类的任何属性之前初始化,因此在派生的构造函数体执行之前。

< P>在C++中保持C++的继承表。添加的虚拟类越多,编译时间(链接)就越长,运行时也就越重


一般来说,如果可以避免使用虚拟类,您可以使用一些模板进行替换,或者尝试以某种方式进行解耦。

这是一个理论问题,还是您最终真的遇到了这样一种情况:它对您有真正的价值实际上,我们有这样的类层次结构(事实上,它们甚至更糟)。计划进行一些重构…这是一个相当不错的继承场景:)!!!!我认为他想去探索虚拟基地课程。虚拟继承调用序列你还必须
F:virtualc
D:virtualb
,否则H将通过E虚拟继承C,而非虚拟继承F。你能解释为什么只有那些需要是虚拟的吗?为什么不是D:虚拟B和F:虚拟C?为什么在这样的类上虚拟函数调用会很慢呢?你是对的,那些可能也需要是虚拟的。您确实需要使用编译器检查它。至于slow,因为编译器需要为每个虚拟函数调用检查六种不同的类型。作为后续问题,您能否解释一下在任何地方使用虚拟继承会产生什么影响(如果有的话)?没有什么?或者我会为I的实例使用不同的(更大的)布局吗?在任何地方使用虚拟继承都会减慢速度,有点慢(尽管我从未测量过)。虚拟继承IMHO的主要缺点是,一旦您将对象强制转换为指向其虚拟基类的指针或引用,就无法将其强制转换回继承类。我不知道您不能向下转换指向虚拟基类的指针。谢谢你指出(没有双关语的意思)。呃,只是为了确保没有误解。您可以将
D*
向下转换为
a*
。但是,你不能向上投射
struct B : virtual A { CONN1(B,A) };
struct C : virtual A { CONN1(C,A)  };
struct D : virtual B { CONN1(D,B) };
struct E : virtual B, virtual C { CONN2(E,B,C) };
struct F : virtual C { CONN1(F,C) };
struct G : D, virtual E { CONN2(G,D,E) };
struct H : virtual E,F { CONN2(H,E,F) };
struct I : G,H { CONN2(I,G,H) };
struct A { int id; A() { id=counter++; } void conn() {} };
struct B : virtual A { CONN1(B,A) };
struct C : virtual A { CONN1(C,A)  };
struct D : virtual B { CONN1(D,B) };
struct E : virtual B, virtual C { CONN2(E,B,C) };
struct F : virtual C { CONN1(F,C) };
struct G : virtual D, virtual E { CONN2(G,D,E) };
struct H : virtual E, virtual F { CONN2(H,E,F) };
struct I : virtual G,virtual H { CONN2(I,G,H) };