16.5. A Generic Handle Class16.5. 一个泛型句柄类
In Chapter 15 we defined two handle classes: the Sales_item (Section 15.8, p. 598) class and the Query (Section 15.9, p. 607) class. These classes managed pointers to objects in an inheritance hierarchy. Users of the handle did not have to manage the pointers to those objects. User code was written in terms of the handle class. The handle dynamically allocated and freed objects of the related inheritance classes and forwarded all "real" work back to the classes in the underlying inheritance hierarchy. 在第十五章定义了两个句柄类:Sales_item 类(第 15.8 节)和 Query 类(第 15.9 节)。这两个类管理继承层次中对象的指针,句柄的用户不必管理指向这些对象的指针,用户代码可以使用句柄类来编写。句柄能够动态分配和释放相关继承类的对象,并且将所有“实际”工作转发给继承层次中的底层类。 These handles were similar to but different from each other: They were similar in that each defined use-counted copy control to manage a pointer to an object of a type in an inheritance hierarchy. They differed with respect to the interface they provided to users of the inheritance hierarchy. 这两个句柄类似但并不相同:类似之处在于都定义了使用计数式的复制控制,管理指向继承层次中某类型对象的指针;不同之处在于它们提供给继承层次用户的接口。 The use-counting implementation was the same in both classes. This kind of problem is well suited to generic programming: We could define a class template to manage a pointer and do the use-counting. Our otherwise unrelated Sales_item and Query types could be simplified by using that template to do the common use-counting work. The handles would remain different as to whether they reveal or hide the underlying inheritance hierarchy. 两个类的使用计数的实现是相同的。这类问题非常适合于泛型编程:可以定义类模板管理指针和进行使用计数。原本不相关的 Sales_item 类型和 Query 类型,可通过使用该模板进行公共的使用计数工作面得以简化。至于是公开还是隐藏下层的继承层次,句柄可以保持不同。 In this section, we'll implement a generic handle class to provide the operations that manage the use count and the underlying objects. Then we'll rewrite the Sales_item class, showing how it could use the generic handle rather than defining its own use-counting operations. 本节将实现一个泛型句柄类(generic handle class),提供管理使用计数和基础对象的操作。然后,我们重新编写 Sales_item 类,展示它怎样使用泛型句柄而不是定义自己的使用计数操作。 16.5.1. Defining the Handle Class16.5.1. 定义句柄类Our Handle class will behave like a pointer: Copying a Handle will not copy the underlying object. After the copy, both Handles will refer to the same underlying object. To create a Handle, a user will be expected to pass the address of a dynamically allocated object of the type (or a type derived from that type) managed by the Handle. From that point on, the Handle will "own" the given object. In particular, the Handle class will assume responsibility for deleting that object once there are no longer any Handles attached to it. Handle 类行为类似于指针:复制 Handle 对象将不会复制基础对象,复制之后,两个 Handle 对象将引用同一基础对象。要创建 Handle 对象,用户需要传递属于由 Handle 管理的类型(或从该类型派生的类型)的动态分配对象的地址,从此刻起,Handle 将“拥有”这个对象。而且,一旦不再有任意 Handle 对象与该对象关联,Handle 类将负责删除该对象。 Given this design, here is an implementation of our generic Handle: 对于这一设计,我们的泛型 Handle 类的实现如下: /* generic handle class: Provides pointerlike behavior. Although access through * an unbound Handle is checked and throws a runtime_error exception. * The object to which the Handle points is deleted when the last Handle goes away. * Users should allocate new objects of type T and bind them to a Handle. * Once an object is bound to a Handle,, the user must not delete that object. */ 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; } } }; This class looks like our other handles, as does the assignment operator. 这个类看来与其他句柄类似,赋值操作符也类似。 template <class T> inline Handle<T>& Handle<T>::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; } The only other members our class will define are the dereference and member access operators. These operators will be used to access the underlying object. We'll provide a measure of safety by having these operations check that the Handle is actually bound to an object. If not, an attempt to access the object will throw an exception. Handle 类将定义的其他成员是解引用操作符和成员访问操作符,这些操作符将用于访问基础对象。让这些操作检查 Handle 是否确实绑定到对象,可以提供一种安全措施。如果 Handle 没有绑定到对象,则试图访问对象将抛出一个异常。 The nonconst versions of these operators look like: 这些操作的非 const 版本看来如下所示: template <class T> inline T& Handle<T>::operator*() { if (ptr) return *ptr; throw std::runtime_error ("dereference of unbound Handle"); } template <class T> inline T* Handle<T>::operator->() { if (ptr) return ptr; throw std::runtime_error ("access through unbound Handle"); } The const versions would be similar and are left as an exercise. 实现一个 Handle 类的自己的版本。 16.5.2. Using the Handle16.5.2. 使用句柄We intend this class to be used by other classes in their internal implementations. However, as an aid to understanding how the Handle class works, we'll look at a simpler example first. This example illustrates the behavior of the Handle by allocating an int and binding a Handle to that newly allocated object: 我们希望 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 Even though the user of Handle allocates the int, the Handle destructor will delete it. In this code, the int is deleted at the end of the outer block when the last Handle goes out of scope. To access the underlying object, we apply the Handle * operator. That operator returns a reference to the underlying int object. 即使是 Handle 的用户分配了 int 对象,Handle 析构函数也将删除它。在外层代码块末尾最后一个 Handle 对象超出作用域时,删除该 int 对象。为了访问基础对象,应用了 Handle 的 * 操作符,该操作符返回对基础 int 对象的引用。 Using a Handle to Use-Count a Pointer使用 Handle 对象对指针进行使用计数As an example of using Handle in a class implementation, we might reimplement our Sales_item class (Section 15.8.1, p. 599). This version of the class defines the same interface, but we can eliminate the copy-control members by replacing the pointer to Item_base by a Handle<Item_base>: 作为在类实现中使用 Handle 的例子,可以重新实现 Sales_item 类(第 15.8.1 节),该类的这个版本定义相同的接口,但可以通过用 Handle<Item_base>: 对象代替 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<Item_base> h; // use-counted handle }; Although the interface to the class is unchanged, its implementation differs considerably from the original: 虽然 Sales_item 类的接口没变,它的实现与原来的相当不同:
The Handle-based version of Sales_item has a single data member. That data member is a Handle attached to a copy of the Item_base object given to the constructor. Because this version of Sales_item has no pointer members, there is no need for copy-control members. This version of Sales_item can safely use the synthesized copy-control members. The work of managing the use-count and associated Item_base object is done inside Handle. 基于 Handle 的 Sales_item 版本有一个数据成员,该数据成员是关联传给构造函数的 Item_base 对象的副本上的 Handle 对象。因为 Sales_item 的这个版本没有指针成员,所以不需要复制控制成员,Sales_item 的这个版本可以安全地使用合成的复制控制成员。管理使用计数和相关 Item_base 对象的工作在 Handle 内部完成。 Because the interface is unchanged, there is no need to change code that uses Sales_item. For example, the program we wrote in Section 15.8.3 (p. 603) can be used without change: 因为接口没变,所以不需要改变使用 Sales_item 类的代码。例如,第 15.8.3 节中编写的程序可以无须改变而使用: double Basket::total() const { double sum = 0.0; // holds the running total /* find each set of items with the same isbn and calculate * the net price for that quantity of items * iter refers to first copy of each book in the set * upper_boundrefers to next element with a different isbn */ 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; } It's worthwhile to look in detail at the statement that calls net_price: 调用 net_price 函数的语句值得仔细分析一下: sum += (*iter)->net_price(items.count(*iter)); This statement uses operator -> to fetch and run the net_price function. What's important to understand is how this operator works: 这个语句使用 -> 操作符获取并运行 net_price 函数,重要的是理解这个操作符怎样工作:
![]() |