const GenericStack& rhs); };
因为类存储的是指针, 可能会出现一个对象被多个堆栈指向的情况(被压入到多个堆栈); 很重要的一点是, pop和类的析构函数销毁任何StackNode对象时, 都不能删除data指针; StackNode对象是在GenericStack类内部分配的, 所以还是得在类的内部释放;
仅仅有GenericStack类是没用的, 很多人容易误用它; e.g. 对于一个保存int的堆栈, 用户会错误地将一个指向Cat对象的指针压入这个堆栈中, 编译不会报错; 因为对于void*参数, 指针类型就可以通过;
为了类型安全, 要为GenericStack创建接口类 interface class:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class IntStack { // int 接口类 public: void push(int *intPtr) { s.push(intPtr); } int * pop() { return static_cast<int*>(s.pop()); } bool empty() const { return s.empty(); } private: GenericStack s; // 实现 }; class CatStack { // cat 接口类 public: void push(Cat *catPtr) { s.push(catPtr); } Cat * pop() { return static_cast
bool empty() const { return s.empty(); } private: GenericStack s; // 实现 }; |
>IntStack和CatStack只是适用于特定类型; 只有int/Cat指针可以被压入或弹出IntStack/CatStack; IntStack和CatStack都通过GenericStack类来实现, 这种关系是通过分层来体现的; IntStack和CatStack将共享GenericStack中真正实现它们行为的函数代码; IntStack和CatStack所有成员函数是(隐式)内联函数, 意味着使用这些接口类带来的开销几乎是零;
但是如果有些用户错误地认为使用GenericStack更高效, 或者轻率地认为类型安全不重要, 怎样才能阻止他们绕过IntStack和CatStack而直接使用GenericStack (设计C++特别要避免类型错误);
要表示 用...来实现, 可以选择私有继承; 通过它可以告诉别人: GenericStack使用起来不安全, 只能用来实现其他的类;
e.g. 将GenericStack的成员函数声明为保护类型:
| 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 | class GenericStack { protected: GenericStack(); ~GenericStack(); void push(void *object); void * pop(); bool empty() const; private: ... // 同上 }; GenericStack s; // 错误! 构造函数被保护 class IntStack: private GenericStack { public: void push(int *intPtr) { GenericStack::push(intPtr); } int * pop() { return static_cast<int*>(GenericStack::pop()); } bool empty() const { return GenericStack::empty(); } }; class CatStack: private GenericStack { public: void push(Cat *catPtr) { GenericStack::push(catPtr); } Cat * pop() { return static_cast
bool empty() const { return GenericStack::empty(); } }; IntStack is; // 正确 CatStack cs; // 也正确 |
>和分层的方法一样, 私有继承的实现避免代码重复, 这样的类型安全的接口类只包含有对GenreicStack函数的内联调用;
在GenericStack类上构筑类型安全的接口是个trick的技巧, 手工写所有的接口类是很麻烦的, 可以使用模板来自动生成它们;
e.g. 模板通过私有继承来生成类型安全的堆栈接口;
| 1 2 3 4 5 6 7 | template<class T> class Stack: private GenericStack { public: void push(T *objectPtr) { GenericStack::push(objectPtr); } T * pop() { return static_cast
bool empty() const { return GenericStack::empty(); } }; |
>编译器会通过这个模板, 根据你的需要自动生成所有的接口类;
因为这些类是类型安全的, 用户类型错误编译器就能发现;
因为GenericStack的成员函数是保护类型, 而且接口把GenericStack作为私有基类, 用户无法绕过接口类;
因为每个接口类成员函数被(隐式)声明为inline, 使用这些类型安全的类时不会带来运行开销; 生成的代码就像用户直接使用GenericStack来编写的一样;
因为GenericStack使用了void*指针, 操作堆栈的代码就只需要一份, 不管程序中使用了多少不同类项的堆栈;
这个设计使得代码达到高效和强力的类型安全;
Note C++的各种特性是以非凡的方式相互作用的;
从这个例子可以发现, 如果使用分层, 达不到这样的效果, 只有继承才能访问保护成员, 只有继承才使得虚函数可以重新被定义; (条款43 虚函数引发私有继承的使用); 因为存储虚函数和保护成员, 有时候私有继承是表达类之间"用...来实现"关系的唯一有效途径; 但从广泛意义上来说, 分层是应该优先采用的技术;
---YC---