Team LiB
Previous Section Next Section

16.5. A Generic Handle Class

16.5. 一个泛型句柄类

This example represents a fairly sophisticated use of C++. Understanding it requires understanding both inheritance and templates fairly well. It may be useful to delay studying this example until you are comfortable with these features. On the other hand, this example provides a good test of your understanding of these features.

这个例子体现了 C++ 相当复杂的语言应用,理解它需要很好地理解继承和模板。在熟悉了这些特性之后再研究这个例子也许会帮助。另一方面,这个例子还能很好地测试你对这些我的理解程序。



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 Class

16.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 类的自己的版本。

Exercises Section 16.5.1

Exercise 16.45:

Implement your own version of the Handle class.

实现一个 Handle 类的自己的版本。

Exercise 16.46:

Explain what happens when an object of type Handle is copied.

解释复制 Handle 类型的对象时会发生什么。

Exercise 16.47:

What, if any, restrictions does Handle place on the types used to instantiate an actual Handle class.

Handle 类对用来实例化实际 Handle 类的类型有限制吗?如果有,限制有哪些?

Exercise 16.48:

Explain what happens if the user attaches a Handle to a local object. Explain what happens if the user deletes the object to which a Handle is attached.

解释如果用户将 Handle 对象与局部对象关联会发生什么。解释如果用户删除 Handle 对象所关联的对象会发生什么。


16.5.2. Using the Handle

16.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 类的接口没变,它的实现与原来的相当不同:

  • Both classes define a default constructor and a constructor that takes a const reference to an Item_base object.

    两个类都定义了默认构造函数和以 Item_base 对象为参数和 const 引用的构造函数。

  • Both define overloaded * and -> operators as const members.

    两个类都将重载的 *-> 操作符定义为 const 成员。

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.

基于 HandleSales_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 函数,重要的是理解这个操作符怎样工作:

  • (*iter) returns h, our use-counted handle member.

    (*iter) 返回 hh 是使用计数式句柄的成员。

  • (*iter)-> therefore uses the overloaded arrow operator of the handle class

    因此,(*iter)-> 使用句柄类的重载箭头操作符。

  • The compiler evaluates h.operator->(), which in turn yields the pointer to Item_base that the Handle holds.

    编译器计算 h.operator->(),获得该 Handle 对象保存的 Item_base 指针。

  • The compiler dereferences that Item_base pointer and calls the net_price member for the object to which the pointer points.

编译器对该 Item_base 指针解引用,并调用指针所指对象的 net_price 成员。

Exercises Section 16.5.2

Exercise 16.49:

Implement the version of the Sales_item handle presented here that uses the generic Handle class to manage the pointer to Item_base.

实现本节提出的 Sales_item 句柄的版本,该版本使用泛型 Handle 类管理 Item_base 指针。

Exercise 16.50:

Rerun the function to total a sale. List all changes you had to make to get your code to work.

重新运行函数计算销售总额。列出让你的代码工作必须进行的所有修改。

Exercise 16.51:

Rewrite the Query class from Section 15.9.4 (p. 613) to use the generic Handle class. Note that you will need to make the Handle a friend of the Query_base class to let it access the Query_base destructor. List and explain all other changes you made to get the programs to work.

重新编写Section 15.9.4第 15.9.4 节Query 类以使用泛型 Handle 类。注意你需要将 Handle 类设为 Query_base 类的友元,以使它能够访问 Query_base 构造函数。列出并解释让程序工作要做的其他所有修改。


Team LiB
Previous Section Next Section