多重继承的缺陷
TemporySecretary
class继承自Secretary
和Tempory
,因此它同时拥有后两者的特性,以及其他可能的更多特性。这导致一种想法:多重继承可能有助于处理”设计组合“----通过少量的、明确选择后的基类。这么一来,使用者借由BaseSmartPtr、MultiThreaded和RefCounter,就可以制作出具有多线程能力、引用计数功能的智能指针。不过,任何一位有经验的类设计者都知道,这样天真的设计方式其实无法运作。
分析多重继承的失败原因,有助于产生根据弹性的设计,这可以对健全的设计方案提供一些有趣的想法。通过多重继承机制来组合多项功能,会产生如下问题:
- 关于技术:目前并没有一成不变即可套用的代码,可以在某种受控情况下将继承而来的类组合起来。唯一可组合BaseSmartPtr、MultiThreaded、RefCounter的工具是语言提供的”多重继承“机制:仅仅是将被组合的基类结合在一起并建立一组用来访问其成员的简单规则。除非情况极为单纯,否则结果难以让人接受。大多数时候你得小心协调继承而来的类的运转,让它们得到所需的行为
- 关于类型信息:基类并没有足够的类型信息来继承完成它们的工作。比如,想象一下,你正试着通过继承一个DeepCopy类
来为你的smart pointer做出深层拷贝。但是DeepCopy应该具有什么样的接口呢?它必须产生一个对象,而类型信息目前未知 - 关于状态处理:基类的各种行为必须操作相同的state(表示数据)。这意味着它们必须虚继承一个持有该state的基类。由于总是有用户类继承library类,这会使设计更加复杂而且变得更没有弹性
虽然本质上是组合,但是多重继承无法单独解决设计时的多样性选择
注意:C++并不提倡多重继承
引入模板
模板是一种很适合”组合各种行为“的机制,主要是因为它们是”依赖使用者提供的类型信息“并且”在编译期才产生“的代码
和一般的类不同,类模板可以以不同的方式定制。如果想要针对特定情况来设计类,你可以在你的类模板中特化其成员函数。举个例子,如果有一个SmartPtr<T>
,你可以针对SmartPtr<Widget>
特化其任何成员函数。这可以为你在设计特化行为时提供更好的粒度。
甚至,对于带有多个参数的类模板,你可以采用偏特化。它可以让你根据部分参数来特化一个类模板。比如:
template<class T, class U> class SmartPtr{...};
你可以令SmartPtr< T, U>针对Widget以及其他任意类型加以特化:
template<class U> class SmartPtr<Widget, U>{...};
模板的编译期特性以及”可相互组合“特性,使它在设计期非常引人注目。然而你一旦开始尝试做这些设计,你会遭遇一些问题:
- 你无法特化结构。单单使用模板,你无法特化”类的结构“(意值器成员数据),你只能特化其成员函数
- 成员函数的特化并不能”依理扩张“。你可以对”单一模板参数“的类模板特化其成员函数,但是无法对”多个模板参数“的类模板特化其个别成员函数
template<class T> class Widget{
void func() { ...一般实现... };
};
// ok
template<> void Widget<char>::Func(){
...特殊实现...
}
template<class T, class U> class Gadget{
void func() { ...一般实现... };
};
// error: 因为这里member function的explicit specialization并无偏特化机制
template<class U> void Gadget<char, U>::func(){
...特殊实现...
}
- 程序库作者不能提供多笔缺省值。理想情况下类模板的作者可以对每个成员函数提供一份缺省作品,但是不能对同一个成员函数提供多份缺省作用。
- 模板为什么要特化,因为编译器认为,对于特定的类型,如果你能对某一功能更好的实现,那么就该听你的。
- 模板分为类模板与函数模板,特化分为全特化与偏特化。全特化就是限定死模板实现的具体类型,偏特化就是如果这个模板有多个类型,那么只限定其中的一部分。
从上面我们可以看出:模板和多重继承是互补的。多重继承欠缺技术,模板有丰富的技术。多重继承缺乏类型信息,而模板中大量存在类型信息。模板的特化无法扩张,多重继承容易扩张。你只能为模板成员函数写一份缺省版本,但是你可以写数量无需的基类
因此,如果我们将模板和多重继承组合起来,将会产生非常具有弹性的设备,应该很适合用于产生程序库的”设计元素“