C++ の 5 つの継承モデルの分析

C++ の 5 つの継承モデルの分析

単一継承

サブクラス (派生クラス) に親クラス (基本クラス) が 1 つだけある場合、それを単一継承と呼びます。 
サブクラス (派生クラス) に複数の親クラス (派生クラス) がある場合、それを多重継承と呼びます。 
まず単一継承モデルを見てみましょう

1.class C
2.{
3.public:
4. C()
5. {
6. cout<<"C()"<<endl;
7. }
8.
9. int _c1;
10.};
11.
12.class B: public C
13.{
14.public:
15. B()
16.
{
17. cout<<"B()"<<endl;
18. }
19.
20. int _b1;
21.
22.};
23.
24.void test()
25.{
26. B b;
27.
b._b1 = 10;
28. b._c1 = 20;
29.}

クラス C には独自のコンストラクターとメンバー変数 _c1 があります。クラス B はクラス C のパブリック継承です。クラス B にも独自のコンストラクターとメンバー変数があります。_c1 と _b1 に値を代入する場合、どちらを最初に呼び出す必要がありますか?

 
メモリ ウィンドウを開いて、構築されたオブジェクトのアドレスを取得します。_c1 の値が最初に割り当てられ、_b1 の値が後で割り当てられていることがわかります。したがって、単一継承モデルでは、基本クラスのメンバー変数が最初に呼び出され、次に派生クラスの値が呼び出されます 

多重継承モデル

サブクラスの継承対象が多数存在する場合があります。基底クラスが複数ある場合を多重継承といいます。一般的な形式は派生 
クラス: 継承メソッド基底クラス1、継承メソッド基底クラス2

1.class C1
2.{
3.public:
4. C1()
5. {
6. cout<<"C1()"<<endl;
7. }
8.
9. int _c1;
10.};
11.
12.class C2
13.{
14.public:
15. C2()
16. {
17. cout<<"C2()"<<endl;
18. }
19.
20. int _c2;
21.};
22.
23.
24.class B: public C1, public C2
25.{
26.public:
27. B()
28. {
29. cout<<"B()"<<endl;
30. }
31.
32. int _b1;
33.
34.};
35.
36.void test()
37.
{
38. B b;
39. b._b1 = 10;
40. b._c1 = 20;
41. b._c2 = 30;
42.}
43.

クラス B の場合、C1 と C2 を継承します。継承順序は、最初に C1 を継承し、次に C2 を継承します。継承方法はパブリック継承です。まず見てみましょう。3 つのクラスのメンバー変数に値を代入すると、派生クラスではどのような継承順序が得られるでしょうか?

 
メモリ ウィンドウを開き、デバッグ メソッドを使用して下を調べます。最初の基本クラスの変数が最初に呼び出され、次に基本クラス 2 の変数、最後に派生クラスの変数が呼び出されていることがわかります。したがって、多重継承モデルでは、最上位層は基本クラス 1、次に基本クラス 2、最後に派生クラスになります。

ダイヤモンドの継承 (ダイヤモンドの継承)

多重継承では、派生クラスの基底クラスが 2 つ存在するため、多重継承と呼ばれます。多重継承の基底クラスが同時に同じ基底クラスを継承する場合、この種の継承はダイヤモンド継承と呼ばれます。別名ダイヤモンド遺産


1.class A
2.{
3.public:
4. A()
5. {
6. cout<<"A()"<<endl;
7. }
8.
9. int _a1;
10.};
11.
12.
13.class C1: public A
14.{
15.public:
16. C1()
17. {
18. cout<<"C1()"<<endl;
19. }
20.
21. int _c1;
22.};
23.
24.class C2: public A
25.{
26.public:
27. C2()
28. {
29. cout<<"C2()"<<endl;
30. }
31.
32. int _c2;
33.};
34.
35.
36.class B: public C1, public C2
37.{
38.public:
39. B()
40. {
41. cout<<"B()"<<endl;
42. }
43.
44. int _b1;
45.
46.};

B センチュリーシティ C1 と C2、および C1 と C2 は A から継承されており、各クラスには独自のメンバー変数があります。派生クラス B を介して _b1、_c1、_c2、_a1、_a2 に値を代入し、コンパイルすると、システムは「_a1 へのアクセスが明確ではありません」というエラーを報告します。これは、B が継承した C1 と C2 には、A から継承したメンバー変数があるため、値を割り当てるためです。 _a1、クラス スコープ修飾子を追加する必要があります (例: b.C1::_a1 = 10)

 
これはいわゆるひし形の継承曖昧性問題であり、メモリ上で B が継承する変数の格納場所は次のようになります。

 
菱形继承实际上就是每一个基类中都有上一层基类的变量,然后派生类把两个基类按继承顺序依次的继承下来。所以菱形继承的一般模型为


虚拟继承

为了解决菱形继承带来的二义性和数据冗余问题,就采取了一种方法:虚拟继承。 
虚拟继承:在继承权限前面加上关键字virtual

1.class C1
2.{
3.public:
4. C1()
5. {
6. cout<<"C1()"<<endl;
7. }
8.
9. int _c1;
10.};
11.
12.
13.class B: virtual public C1
14.{
15.public:
16. B()
17. {
18. cout<<"B()"<<endl;
19. }
20.
21. int _b1;
22.
23.};
24.
25.void test()
26.
{
27. B b;
28. b._b1 = 10;
29. b._c1 = 20;
30.}

现在,让B虚拟继承C1,然后给C1和B里面的变量依次赋值,是不是还和单继承或多继承一样,基类的数据存放在前面。打开内存窗口,然后对b取地址,可以看到先是一串数字,然后是0a(派生类变量)再是14(基类变量),那第一行中的数字是什么意思?

 
这串数字占了4个字节,所以应该想到他是一个地址,那就好办。再打开一个内存窗口,将这个地址查看一下(注:VS里存储数据是小端存储)

 
进入这个地址中去,第一行是00 00 00 00 ,第二行是08 00 00 00 ,先来解释一下这两行的值代表什么含义。00 00 00 00 表示这块地址相对于自己的偏移量, 08 00 00 00 表示相对基类的偏移量。而这块表就叫做偏移量表格。所以对于一般的虚拟继承都有以下的模型来解释他


菱形虚拟继承

在刚刚的菱形继承模型中,最后发现会存在二义性和数据冗余的问题,而虚拟继承不用存储基类的基类的值,改为了存偏移量表格,然后将基类放在最下面,这就消除了二义性问题,那么用虚拟继承解决菱形继承的问题就叫做菱形虚拟继承

1.class A
2.{
3.public:
4. A()
5. {
6. cout<<"A()"<<endl;
7. }
8.
9. int _a1;
10.};
11.
12.
13.class C1: virtual public A
14.{
15.public:
16. C1()
17. {
18. cout<<"C1()"<<endl;
19. }
20.
21. int _c1;
22.};
23.
24.class C2: virtual public A
25.{
26.public:
27. C2()
28. {
29. cout<<"C2()"<<endl;
30. }
31.
32. int _c2;
33.};
34.
35.
36.class B: public C1, public C2
37.{
38.public:
39. B()
40. {
41. cout<<"B()"<<endl;
42. }
43.
44. int _b1;
45.
46.};
47.
48.void test()
49.
{
50. B b;
51. b._b1 = 10;
52. b._c1 = 20;
53. b._c2 = 30;
54. b.C2::_a1 = 2;
55.}
56.

在菱形继承中,关键字virtual是在基类的上面加,而不是在派生类继承时加。

 
虚拟继承时,是在派生类里加一个指针,那么现在的菱形继承中指针是在C1还是C2处加。首先B肯定是在下面,现在C1和C2都继承于A,那么A就是C1和C2的下面,而A和B的顺序是什么样的呢? 
打开内存窗口,分别给成员变量赋值之后可以看到A是在最下面的,而 a0 dc 19 01 和 ac dc 29 01 在这就应该是C1和C2的偏移量表格指针

 
再进一步查看一下指向偏移量表格的指针

 
果然,和虚拟继承一样,在偏移量表格里,都有相对自己的偏移量和相对基类的偏移量,按顺序继承下来之后,C1在最上面所以相对基类A的偏移量就是20,然后继承的是C2,相对基类A的偏移量就是12。而虚拟继承刚开始说是为了解决菱形继承带来的二义性问题,那么现在能够直接通过B给A里面的变量赋值吗?

 
编译之后没问题,这样菱形继承所带来的二义性问题也就得到解决了。再来看看菱形虚拟继承的一半模型


おすすめ

転載: blog.csdn.net/prokgtfy9n18/article/details/76045460
おすすめ