C++多重继承与虚拟继承

  本文只是粗浅讨论一下C++中的多重继承和虚拟继承。

多重继承中的构造函数和析构函数调用次序

  我们先来看一下简单的例子:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class A
 5 {
 6 private:
 7     char idA;
 8 
 9 public:
10     A(){ 
11         idA = 'A';
12         cout << "Constructor of A is called!" << endl; 
13     }
14     ~A() { cout << "Destructor of A is called!" << endl; }
15 };
16 
17 class B : public A
18 {
19 private:
20     char idB;
21 
22 public:
23     B(){
24         idB = 'B';
25         cout << "Constructor of B is called!" << endl;
26     }
27     ~B() { cout << "Destructor of B is called!" << endl; }
28 };
29 
30 class C : public A
31 {
32 private:
33     char idC;
34 
35 public:
36     C(){
37         idC = 'C';
38         cout << "Constructor of C is called!" << endl;
39     }
40     ~C() { cout << "Destructor of C is called!" << endl; }
41 };
42 
43 class D : public B, public C
44 {
45 private:
46     char idD;
47 
48 public:
49     D(){
50         idD = 'D';
51         cout << "Constructor of D is called!" << endl;
52     }
53     ~D() { cout << "Destructor of D is called!" << endl; }
54 };
55 
56 int main()
57 {
58     D d;
59     return 0;
60 }

  上述程序的输出为:

  

  由上边结果可以看出,析构函数调用次序跟构造函数是相反的。另外,构造函数调用次序跟类D继承B、C次序(public B, public C)相关。

  可能我们也发现了,对于类D的实例d来说,它其实有两个重复的A实例。我们应该要去掉其中一个以节省空间。具体做法就是采用虚拟继承的方法:

1 class B : public virtual A
2 {
3     ...
4 };
5 
6 class C : public virtual A
7 {
8     ...
9 };

  这是程序的输出就会变成:

  

  可见这个时候类D的实例d就只有一个类A实例。

二义性

  请看下边程序:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class A
 5 {
 6 private:
 7     char idA;
 8 
 9 public:
10     A(){ 
11         idA = 'A';
12         cout << "Constructor of A is called!" << endl; 
13     }
14     ~A() { cout << "Destructor of A is called!" << endl; }
15     char getID() { return idA; }
16 };
17 
18 class B  : public virtual A
19 {
20 private:
21     char idB;
22 
23 public:
24     B(){
25         idB = 'B';
26         cout << "Constructor of B is called!" << endl;
27     }
28     ~B() { cout << "Destructor of B is called!" << endl; }
29     char getID() { return idB; }
30 };
31 
32 class C : public virtual A
33 {
34 private:
35     char idC;
36 
37 public:
38     C(){
39         idC = 'C';
40         cout << "Constructor of C is called!" << endl;
41     }
42     ~C() { cout << "Destructor of C is called!" << endl; }
43     char getID() { return idC; }
44 };
45 
46 class D : public B, public C
47 {
48 private:
49     char idD;
50 
51 public:
52     D(){
53         idD = 'D';
54         cout << "Constructor of D is called!" << endl;
55     }
56     ~D() { cout << "Destructor of D is called!" << endl; }
57     // char getID() { return idD; }
58 };
59 
60 int main()
61 {
62     D d;
63     cout << d.getID() << endl;
64 
65     return 0;
66 }

  在main函数中,第63行的d.getID()会优先在类D中查找有没有getID()的定义,如果没有就会到其父类查找;而恰好其父类B、C(同级)均定义了相同的getID()(类A的getID()定义存不存在都没关系),这时d.getID()就不知道要调用B类中的getID()还是C类中的,从而导致二义性

  不过我们可以通过d.B::getID()、d.C::getID()来指明具体要调用哪一个类的getID。但我们总不会想到这样子去做,而且这样子做也比较麻烦。

虚函数

  对于多重继承的虚函数同样存在二义性。

  先看一下程序:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class A
 5 {
 6 private:
 7     char idA;
 8 
 9 public:
10     A(){ 
11         idA = 'A';
12         cout << "Constructor of A is called!" << endl; 
13     }
14     ~A() { cout << "Destructor of A is called!" << endl; }
15     char getID() { return idA; }
16 };
17 
18 class B : public virtual A
19 {
20 private:
21     char idB;
22 
23 public:
24     B(){
25         idB = 'B';
26         cout << "Constructor of B is called!" << endl;
27     }
28     ~B() { cout << "Destructor of B is called!" << endl; }
29     char getID() { return idB; }
30 };
31 
32 class C : public virtual A
33 {
34 private:
35     char idC;
36 
37 public:
38     C(){
39         idC = 'C';
40         cout << "Constructor of C is called!" << endl;
41     }
42     ~C() { cout << "Destructor of C is called!" << endl; }
43     char getID() { return idC; }
44 };
45 
46 class D : public B, public C
47 {
48 private:
49     char idD;
50 
51 public:
52     D(){
53         idD = 'D';
54         cout << "Constructor of D is called!" << endl;
55     }
56     ~D() { cout << "Destructor of D is called!" << endl; }
57     char getID() { return idD; }
58 };
59 
60 int main()
61 {
62     D d;
63     A a = d;
64     B b = d;
65     C c = d;
66     cout << a.getID() << endl;
67     cout << b.getID() << endl;
68     cout << c.getID() << endl;
69     cout << d.getID() << endl;
70 
71     return 0;
72 }

  程序输出如下:

  

  上边程序第63~65行相当于a、b、c将d进行了分割(函数是否是虚函数在这里并无关系,而且注意这里的a、b、c、d都不是指针),分割出属于自己的部分,所以调用getID()的时候能正确反映具体的类。

  

  我们再来看一个程序:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class A
 5 {
 6 private:
 7     char idA;
 8 
 9 public:
10     A(){ 
11         idA = 'A';
12         cout << "Constructor of A is called!" << endl; 
13     }
14     ~A() { cout << "Destructor of A is called!" << endl; }
15     virtual char getID() { return idA; }
16 };
17 
18 class B : public virtual A
19 {
20 private:
21     char idB;
22 
23 public:
24     B(){
25         idB = 'B';
26         cout << "Constructor of B is called!" << endl;
27     }
28     ~B() { cout << "Destructor of B is called!" << endl; }
29     virtual char getID() { return idB; }
30 };
31 
32 class C : public virtual A
33 {
34 private:
35     char idC;
36 
37 public:
38     C(){
39         idC = 'C';
40         cout << "Constructor of C is called!" << endl;
41     }
42     ~C() { cout << "Destructor of C is called!" << endl; }
43     virtual char getID() { return idC; }
44 };
45 
46 class D : public B, public C
47 {
48 private:
49     char idD;
50 
51 public:
52     D(){
53         idD = 'D';
54         cout << "Constructor of D is called!" << endl;
55     }
56     ~D() { cout << "Destructor of D is called!" << endl; }
57     virtual char getID() { return idD; }
58 };
59 
60 int main()
61 {
62     D *d = new D();
63     A *a = d;
64     B *b = d;
65     C *c = d;
66     cout << a->getID() << endl;
67     cout << b->getID() << endl;
68     cout << c->getID() << endl;
69     cout << d->getID() << endl;
70 
71     delete d;
72     return 0;
73 }

  程序的输出如下:

  

  从输出结果可以看出,类D的getID覆盖其所有父类的getID。需要注意的是,当我们在类D的一个父类,如A中不设定getID为虚函数,则“A *a = d”的效果仍然跟分割d指向的内存的效果一样。

原文地址:https://www.cnblogs.com/xiehongfeng100/p/4708567.html