引言:
【小心地雷】
这个例子体现了C++相当复杂的语言应用,理解它需要很好地理解继承和模板。在熟悉了这些特性之后再研究这个例子也许会帮助。另一方面,这个例子还能很好地测试你对这些特性的理解程度。
前面示例的Sales_item和Query两个类的使用计数的实现是相同的。这类问题非常适合于泛型编程:可以定义类模板管理指针和进行使用计数。原本不相关的Sales_item类型和 Query类型,可通过使用该模板进行公共的使用计数工作而得以简化。至于是公开还是隐藏下层的继承层次,句柄可以保持不同。
一、定义句柄类
Handle类行为类似于指针:复制Handle对象将不会复制基础对象,复制之后,两个Handle对象将引用同一基础对象。要创建Handle对象,用户需要传递属于由Handle管理的类型(或从该类型派生的类型)的动态分配对象的地址,从此刻起,Handle将“拥有”这个对象。而且,一旦不再有任意Handle对象与该对象关联,Handle类将负责删除该对象。
Handle类:
template <class T> class Handle { public: Handle(T *p = 0):ptr(p),use(new size_t(1)) {} Handle(const Handle &h):ptr(h.ptr),use(h.use) { ++ *use; } Handle &operator=(const Handle &rhs); ~Handle() { rem_ref(); } T &operator*(); T *operator->(); const T &operator*() const; const T *operator->() const; private: T *ptr; size_t *use; void rem_ref() { if (-- *use == 0) { delete use; delete ptr; } } };
赋值操作符:
template <class Type> Handle<Type> &Handle<Type>::operator=(const Handle<Type> &rhs) { ++ *rhs.use; rem_ref(); ptr = rhs.ptr; use = rhs.use; return *this; }
解引用操作符和成员访问操作符[+P562习题16.45]:
如果Handle没有绑定到对象,则试图访问对象时将抛出一个异常:
template <typename Type> Type &Handle<Type>::operator*() { if (ptr) { return *ptr; } throw std::runtime_error("dereference of unbound Handle"); } template <typename Type> Type *Handle<Type>::operator->() { if (ptr) { return ptr; } throw std::runtime_error("access through unbound Handle"); } template <typename Type> const Type &Handle<Type>::operator*() const { if (ptr) { return *ptr; } throw std::runtime_error("dereference of unbound Handle"); } template <typename Type> const Type *Handle<Type>::operator->() const { if (ptr) { return ptr; } throw std::runtime_error("access through unbound Handle"); }
二、使用句柄
我们希望Handle类能够用于其他类的内部实现中。
一个简单的示例:通过分配一个int对象,并将一个Handle对象绑定到新分配的int对象而说明Handle的行为:
{ Handle<int> hp(new int(42)); { Handle<int> hp2 = hp; cout << *hp << " " << *hp2 << endl; //42 42 *hp2 = 10; } cout << *hp << endl; //10 }
即使是Handle的用户分配了int对象,Handle析构函数也将删除它。在外层代码末尾最后一个Handle对象超出作用域时,删除该int对象。
使用Handle对象对指针进行使用计数
可以重新实现Sales_item类,在类中使用Handle,该类的这个版本定义相同的接口,但可以通过用Handle<Item_base>对象代替Item_base指针而删除复制控制成员:
class Sales_item { public: Sales_item():h() {} Sales_item(const Item_base &item):h(item.clone()) {} const Item_base &operator*() const { return *h; } const Item_base *operator->() const { return h.operator -> (); } private: Handle<Item_base> h; };
因为Sales_item的这个版本没有指针成员,所以不需要复制控制成员,Sales_item的这个版本可以安全的使用合成的复制控制成员。管理使用计数和相关Item_base对象的工作在Handle内部完成。
因为接口没变,所以不需要改变使用Sales_item类的代码。如:
double Basket::total() const { double sum = 0.0; for (const_iter iter = items.begin(); iter != items.end(); iter = items.upper_bound(*iter)) { sum += (*iter)->net_price(items.count(*iter)); } return sum; }
分析:
sum += (*iter)->net_price(items.count(*iter));
1)(*iter)返回h,h是使用计数式句柄的成员;
2)因此,(*iter)->使用句柄类的重载箭头操作符;
3)编译器计算h.operator->(),获得Handle对象保存的Item_base指针;
4)编译器对该Item_base指针解引用,并调用指针所指向对象的Item_base成员。
//P564 习题16.51 class Query { friend Query operator~(const Query &); friend Query operator|(const Query &,const Query &); friend Query operator&(const Query &,const Query &); public: Query(const string &); set<TextQuery::line_no> eval(const TextQuery &t) const { return h -> eval(t); } ostream &display(ostream &os) const { return h -> display(); } private: Query(Query_base *query):h(query) {} Handle<Query_base> h; }; /** *其他操作与前相似,在此不再赘述 */