16.5. 一个泛型句柄类
这个例子体现了 C++ 相当复杂的语言应用,理解它需要很好地理解继承和模板。在熟悉了这些特性之后再研究这个例子也许会帮助。另一方面,这个例子还能很好地测试你对这些我的理解程序。
在第十五章定义了两个句柄类:Sales_item 类(第 15.8 节)和 Query 类(第 15.9 节)。这两个类管理继承层次中对象的指针,句柄的用户不必管理指向这些对象的指针,用户代码可以使用句柄类来编写。句柄能够动态分配和释放
相关继承类的对象,并且将所有“实际”工作转发给继承层次中的底层类。这两个句柄类似但并不相同:类似之处在于都定义了使用计数式的复制控制,管理指向继承层次中某类型对象的指针;不同之处在于它们提供给继承层次用户的接口。
两个类的使用计数的实现是相同的。这类问题非常适合于泛型编程:可以定义类模板管理指针和进行使用计数。原本不相关的 Sales_item 类型和 Query类型,可通过使用该模板进行公共的使用计数工作面得以简化。至于是公开还是
隐藏下层的继承层次,句柄可以保持不同。
本节将实现一个泛型句柄类(generic handle class),提供管理使用计数和基础对象的操作。然后,我们重新编写 Sales_item 类,展示它怎样使用泛型句柄而不是定义自己的使用计数操作。
16.5.1. 定义句柄类
Handle 类行为类似于指针:复制 Handle 对象将不会复制基础对象,复制之后,两个 Handle 对象将引用同一基础对象。要创建 Handle 对象,用户需要传递属于由 Handle 管理的类型(或从该类型派生的类型)的动态分配对象的地址,从此刻起,Handle 将“拥有”这个对象。而且,一旦不再有任意 Handle 对象与该对象关联,Handle 类将负责删除该对象。
对于这一设计,我们的泛型 Handle 类的实现如下:
template <class T> class Handle {
public:
// unbound handle
Handle(T *p = 0): ptr(p), use(new size_t(1)) { }
// overloaded operators to support pointer behavior
T& operator*();
T* operator->();
const T& operator*() const;
const T* operator->() const;
// copy control: normal pointer behavior, but last Handle
deletes the object
Handle(const Handle& h): ptr(h.ptr), use(h.use)
{ ++*use; }
Handle& operator=(const Handle&);
~Handle() { rem_ref(); }
private:
T* ptr; // shared object
size_t *use; // count of how many Handle spointto *ptr
void rem_ref()
{ if (--*use == 0) { delete ptr; delete use; } }
};
这个类看来与其他句柄类似,赋值操作符也类似。
template
inline Handle& Handle::operator=(const Handle &rhs)
{
++*rhs.use; // protect against self-assignment
rem_ref(); // decrement use count and delete pointers if
needed
ptr = rhs.ptr;
use = rhs.use;
return *this;
}
Handle 类将定义的其他成员是解引用操作符和成员访问操作符,这些操作符将用于访问基础对象。让这些操作检查 Handle 是否确实绑定到对象,可以提供一种安全措施。如果 Handle 没有绑定到对象,则试图访问对象将抛出一个异常。
这些操作的非 const 版本看来如下所示:
template <class T> inline T& Handle::operator*()
{
if (ptr) return *ptr;
throw std::runtime_error
("dereference of unbound Handle");
}
template <class T> inline T* Handle::operator->()
{
if (ptr) return ptr;
throw std::runtime_error
("access through unbound Handle");
}
实现一个 Handle 类的自己的版本。
Exercises Section 16.5.1
Exercise
16.45:
实现一个 Handle 类的自己的版本。
Exercise
16.46:
解释复制 Handle 类型的对象时会发生什么。
Exercise
16.47:
Handle 类对用来实例化实际 Handle 类的类型有限制吗?如果有,限制有哪些?
Exercise
16.48:
解释如果用户将 Handle 对象与局部对象关联会发生什么。解释如果用户删除 Handle 对象所关联的对象会发生
什么。
16.5.2. 使用句柄
我们希望 Handle 类能够用于其他类的内部实现中。但是,为了帮助理解Handle 类怎样工作, 交首先介绍一个较简单的例子。 这个例子通过分配一个 int对象, 并将一个 Handle 对象绑定到新分配的 int 对象而说明 Handle 的行为:
{ // new scope
// user allocates but must not delete the object to which the Handle
is attached
Handle<int> hp(new int(42));
{ // new scope
Handle<int> hp2 = hp; // copies pointer; use count incremented
cout << *hp << " " << *hp2 << endl; // prints 42 42
*hp2 = 10; // changes value of shared underlying int
} // hp2 goes out of scope; use count is decremented
cout << *hp << endl; // prints 10
} // hp goes out of scope; its destructor deletes the int
即使是 Handle 的用户分配了 int 对象,Handle 析构函数也将删除它。在外层代码块末尾最后一个 Handle 对象超出作用域时,删除该 int 对象。为了访问基础对象,应用了 Handle 的 * 操作符,该操作符返回对基础 int 对象的引用。
使用 Handle 对象对指针进行使用计数作为在类实现中使用 Handle 的例子,可以重新实现 Sales_item 类(第15.8.1 节),该类的这个版本定义相同的接口,但可以通过用Handle
对象代替 Item_base 指针而删去复制控制成员:
class Sales_item {
public:
// default constructor: unbound handle
Sales_item(): h() { }
// copy item and attach handle to the copy
Sales_item(const Item_base &item): h(item.clone()) { }
// no copy control members: synthesized versions work
// member access operators: forward their work to the Handle
class
const Item_base& operator*() const { return *h; }
const Item_base* operator->() const
{ return h.operator->(); }
private:
Handle h; // use-counted handle
};
虽然 Sales_item 类的接口没变,它的实现与原来的相当不同:
• 两个类都定义了默认构造函数和以 Item_base 对象为参数和 const 引用的构造函数。
• 两个类都将重载的 * 和 -> 操作符定义为 const 成员。
基于 Handle 的 Sales_item 版本有一个数据成员,该数据成员是关联传给构造函数的 Item_base 对象的副本上的 Handle 对象。因为 Sales_item 的这个版本没有指针成员,所以不需要复制控制成员,Sales_item 的这个版本可以
安全地使用合成的复制控制成员。管理使用计数和相关 Item_base 对象的工作在 Handle 内部完成。
因为接口没变, 所以不需要改变使用 Sales_item 类的代码。 例如, 第 15.8.3节中编写的程序可以无须改变而使用:
double Basket::total() const
{
double sum = 0.0; // holds the running total
for (const_iter iter = items.begin();
iter != items.end();
iter = items.upper_bound(*iter))
{
// we know there's at least one element with this key in the
Basket
// virtual call to net_priceapplies appropriate discounts,
if any
sum += (*iter)->net_price(items.count(*iter));
}
return sum;
}
调用 net_price 函数的语句值得仔细分析一下:
sum += (*iter)->net_price(items.count(*iter));
这个语句使用 -> 操作符获取并运行 net_price 函数, 重要的是理解这个操
作符怎样工作:
• (*iter) 返回 h,h 是使用计数式句柄的成员。
• 因此,(*iter)->
使用句柄类的重载箭头操作符。
• 编译器计算 h.operator->(),
获得该 Handle 对象保存的 Item_base 指
针。
• 编译器对该 Item_base 指针解引用,并调用指针所指对象的 net_price
成员。
Exercises Section 16.5.2
Exercise
16.49:
实现本节提出的 Sales_item 句柄的版本,该版本使用泛型 Handle 类管理 Item_base 指针。
Exercise
16.50:
重新运行函数计算销售总额。列出让你的代码工作必须进行的所有修改。
Exercise
16.51:
重新编写 Section 15.9.4 第 15.9.4 节的 Query 类以使用泛型 Handle 类。注意你需要将 Handle 类设为
Query_base 类的友元,以使它能够访问 Query_base 构造函数。列出并解释让程序工作要做的其他所有修改。