一个泛型句柄类--C++模板和泛型编程--c++ primer
2015-03-24 15:15
330 查看
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 类的实现如下:
这个类看来与其他句柄类似,赋值操作符也类似。
Handle 类将定义的其他成员是解引用操作符和成员访问操作符,这些操作符将用于访问基础对象。让这些操作检查 Handle 是否确实绑定到对象,可以提供一种安全措施。如果 Handle 没有绑定到对象,则试图访问对象将抛出一个异常。
这些操作的非 const 版本看来如下所示:
实现一个 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 的行为:
即使是 Handle 的用户分配了 int 对象,Handle 析构函数也将删除它。在外层代码块末尾最后一个 Handle 对象超出作用域时,删除该 int 对象。为了访问基础对象,应用了 Handle 的 * 操作符,该操作符返回对基础 int 对象的引用。
使用 Handle 对象对指针进行使用计数作为在类实现中使用 Handle 的例子,可以重新实现 Sales_item 类(第15.8.1 节),该类的这个版本定义相同的接口,但可以通过用
虽然 Sales_item 类的接口没变,它的实现与原来的相当不同:
• 两个类都定义了默认构造函数和以 Item_base 对象为参数和 const 引用的构造函数。
• 两个类都将重载的 * 和 -> 操作符定义为 const 成员。
基于 Handle 的 Sales_item 版本有一个数据成员,该数据成员是关联传给构造函数的 Item_base 对象的副本上的 Handle 对象。因为 Sales_item 的这个版本没有指针成员,所以不需要复制控制成员,Sales_item 的这个版本可以
安全地使用合成的复制控制成员。管理使用计数和相关 Item_base 对象的工作在 Handle 内部完成。
因为接口没变, 所以不需要改变使用 Sales_item 类的代码。 例如, 第 15.8.3节中编写的程序可以无须改变而使用:
调用 net_price 函数的语句值得仔细分析一下:
这个语句使用 -> 操作符获取并运行 net_price 函数, 重要的是理解这个操
作符怎样工作:
• (*iter) 返回 h,h 是使用计数式句柄的成员。
• 因此,
• 编译器计算
针。
• 编译器对该 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 构造函数。列出并解释让程序工作要做的其他所有修改。
这个例子体现了 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 <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; }
Handle 类将定义的其他成员是解引用操作符和成员访问操作符,这些操作符将用于访问基础对象。让这些操作检查 Handle 是否确实绑定到对象,可以提供一种安全措施。如果 Handle 没有绑定到对象,则试图访问对象将抛出一个异常。
这些操作的非 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"); }
实现一个 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>:对象代替 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 };
虽然 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 构造函数。列出并解释让程序工作要做的其他所有修改。
相关文章推荐
- C++ Primer 学习笔记_83_模板与泛型编程 --一个泛型句柄类
- C++ Primer 学习笔记_83_模板与泛型编程 -一个泛型句柄类
- C++的一个简单的句柄类模板
- [C/C++] 第16章 模板与泛型编程 《 C++ Primer 》
- c++模板与泛型编程(一)模板定义 ——《c++ primer》读书笔记
- [C/C++] 第16章 模板与泛型编程 《 C++ Primer 》
- 12.29--C++模板与泛型编程--《C++ Primer》学习
- Effective C++第七章-模板和泛型编程之需要类型转换时请为模板定义非成员函数
- C++菜鸟学习笔记——模板与泛型编程
- 《C++ Primer》 第四版 第16章 模板与泛型编程
- effective c++ -- 模板与泛型编程
- 《C++ primer》之 第15章-16章 面向对象、模板、泛型编程
- Effective C++ --7 模板与泛型编程
- C++模板与泛型编程
- C++ Primer 学习笔记_86_模板与泛型编程 -满载与函数模板
- 模板和泛型编程--模板编译模型--第十六章 --c++ primer(3)
- C++ 模板类型萃取技术 第二部分 基于泛型的类型萃取技术
- C++ Primer 学习笔记_85_模板与泛型编程 --模板特化[续]
- [C++ Primer] : 第16章: 模板与泛型编程
- C++模板与泛型编程(二)——类模板