这里主要是关于私有继承和多重继承方面需要注意的一点东西

在C++中使用面向对象思想的类继承,我觉得最关键的还是要理解父类是如何把成员继承给派生类的。

我的理解就是父类其实以一种独立的未命名的对象存在于派生类中,就好象包含关系一样。

只要理解了上面这点,在写派生类的各种方法(构造函数,赋值函数等)的时候把父类甚至是父类的父类当作独立的未命名的对象来处理关系就会非常的明确了。

私有继承

私有继承的作用和包含差不多都是has-a关系,但是在处理起来没有包含那么的直接。
例如如果要返回包含在派生类中父类的对象时,若是包含关系可以直接通过公有接口调用返回所包含对象即可。但是在私有继承中父类没有具体的名字也就不能直接返回父类对象了,唯一可以返回和使用的对象就是派生类本身*this,所以通常情况下返回父类对象是通过将*this进行up-casting强制类型转换,

1
2
// 这是一共私有继承string类的一个派生类中
cout << (const std::string &)(*this);

对于私有继承中的方法,派生类继承的是父类方法的实现,并没有继承父类方法的接口。也就是说,与数据成员不同,私有继承的方法是可以在派生类内部通过作用域解析运算符来调用的,但是外部世界无法访问父类的任何数据和方法


多重继承(MI)

关于多重继承第一次看书的时候也是感觉里面的关系很绕,主要是要把代码进行重新整理以避免在多重继承中出现普通继承中不会出现的问题例如各种二义性
对此要做出的调整我在这里总结下:

先写个简单的例子说明下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class A: // 祖先类
{
private:
int a;
public:
...
}
class B : virtual public A
{
private:
int b;
public:
...
}
class C : virtual public A
{
private:
int c;
public:
...
}
class D : public C, public B
{
public:
...
}
class E : public A
{
private:
int e;
public:
...
}
class F : public E
{
private:
int f;
public:
...
}

使用虚基类是避免出现派生类中出现两个祖先导致二义性的特性。通过使用虚基类本质上来说,第二代类对象共享一个第一代A对象,而不是各自引入自己的第一代A对象的副本。

构造函数中

在构造函数中要注意,如果没有使用虚基类,则类继承中会有信息传递,即F的构造函数初始化父类E对象和数据成员f,初始化E对象的时候会初始化E的父类A对象和数据成员e。但是这种传递在多重继承时便会出现同时有两个祖先类的二义性。
所以使用虚基类的时候这种信息传递将不会起作用,即不会自动调用A的构造函数,然后通过A类的默认构造函数初始化公用的A对象。
如果不希望默认构造函数来构造虚基类对象,则要显式的调用所需的基类构造函数(我一般都这样显式调用,这样会比较清晰)

1
2
3
4
D:D(const A & a, int b, int c)
: A(a), B(A, b), C(A, c)
{ // 当然要在B,C中定义相应的构造函数
}

另外在写代码的时候也会遇到一种构造函数的情况:

1
2
3
4
highfink::highfink(const fink & f, int ico)
: abstr_emp(f), fink(f), manager((const abstr_emp &)f, ico) // ????
{
}

在这里为了初始化manager,我先讲fink类强制转换类型成为基类然后使用转换后的基类和另一个成员数据初始化manager。这是我按照理解进行的处理,可以通过编译。如果以后遇到这种情况还有其他方法我在写上来。

使用哪个方法?

因为可能在两个父类中有名称相同的成员函数,那么在子类调用的时候是调用那个父类的成员方法呢?
这种避免二义性的方法是使用模块化方式
即通过在父类中写几个模块化的函数并protected起来,然后通过使用作用域解析运算符来组合成子类的相应的成员方法。例如:

1
2
3
4
5
6
void SingerWaiter::Show() const
{
cout << "Category: singing waiter\n";
Worker::Data();
Data();
}

总结

在祖先相同时,使用MI必须引入虚基类,并修改构造初始化列表的规则。同时如果编写这些类的时候没有考虑到MI,则还可能要重新编写他们。

Comments

2016-03-10