19.8.4 接口强制操作符的实现
你可能早就忍不住想知道这些强制操作符到底是如何工作的了,那么就让我们来考察一下吧。前面提到的3个接口强制类都是从interface_cast_base模板派生来的,继承方式是受保护(protected)继承。只不过这3者实行继承的具体方式略有不同。interface_cast_noaddref是这样的:
程序清单19.15
- template< typename I
- , typename X = throw_bad_interface_cast_exception
- >
- class interface_cast_noaddref
- : protected interface_cast_base<I, noaddref_release<I>, X>
- {
- public:
- typedef interface_cast_base<. . . > parent_class_type;
- typedef I interface_pointer_type;
- typedef . . . protected_pointer_type;
- public:
- template <typename J>
- explicit interface_cast_noaddref(J &j)
- : parent_class_type(j)
- {}
- explicit interface_cast_noaddref(interface_pointer_type pi)
- : parent_class_type(pi)
- {}
- public:
- protected_pointer_type operator -> () const
- {
- return static_cast<. . .>(parent_class_type::get_pointer());
- }
- // 声明但不予实现
- private:
- . . . // 不可访问的拷贝构造函数与拷贝赋值操作符
- };
从上面的代码可以看出,interface_cast_noaddref的父类是interface_cast_base的一个特化,特化它的主要参数包括noaddref_release仿函数类以及给定的异常策略类。noaddref_release仿函数负责释放在构造函数中获取的接口,以便确保被强制的COM实例上的引用计数从总体上既未增大也未减小。
interface_cast_noaddref具有模板形式的构造函数,这就允许用户对任何类型的COM接口进行强制。由于这是一个需要一定开销的操作,所以该强制提供了第二个构造函数,它接受强制目标类型的指针,该构造函数的实现简单(高效)地在它接受的指针参数上调用AddRef()。两个构造函数都会转发至基类中与它们对应的构造函数。
另外一个值得注意的方面是,用户只能通过调用该强制的operator->() const方法来访问查询到的接口,这个特性有助于确保该强制的安全性。由于该强制并未提供任何隐式转换操作符,所以编译器就会拒绝如下形式的代码,否则这段代码中潜在的死引用(dead reference)危机就可能发作:
- IX *px = interface_cast_noaddref<IX>(py); // 编译错误
- px->SomeMethod(); // 崩溃!不过幸运的是,上边的代码根本不能通过编译
这是典型的苦行僧式编程(www.cppentry.com):通过编译器跟语言的通力合作来防止人们误用我们的类型。然而,在类似下面的这种场景下,这种做法倒确实令该强制变得难于使用了:- func(IX *px);
- IY *py = . . .;
- func(interface_cast_noaddref<IX>(p)); // 编译错误
COM规则明确表示:当你将一个接口引用传递给一个函数时,除非该函数在它的语义里面明确表示,否则该函数就不拥有这个接口引用。因此,尽管这里改用interface_cast_addref就能通过编译,但这样做就会留下一个悬挂引用。所以说,interface_cast_noaddref正是我们需要的。
当然,只要有需求,就会有解决之道。如果你刚愎自用的话,你总可以这么写:
- IStream *pi = interface_cast_noaddref<IStream, . . .>(p).operator ->();
- pi->SomeMethod(); // 未定义行为!可能崩溃
但你要是这么做的话,我可就要叫"C++(www.cppentry.com)警察"了,你会遭到这样的责令:"别再编码了!回家去吧,呆上一年再说"。当然,由于不完美主义编程(www.cppentry.com)告诉我们要尊重有经验的开发者的意愿,所以这里有一个解决方案。我们可以使用一个相关联的get_ptr()属性垫片(Attribute Shim,我们将会在下一章涉及垫片概念的方方面面),像这样: - func(get_ptr(interface_cast_noaddref<IX>(p))); // 没问题
另外两个接口强制的实现类似于interface_cast_noaddref。interface_cast_test使用了"惰性异常"策略类型,即ignore_interface_cast_exception,这样一来即使强制失败也不会导致异常的抛出,而是通过布尔类型的隐式转换操作符来体现。 interface_cast_test使用了noaddref_release来确保COM对象的引用计数既不增也不减。
程序清单19.16
- template<typename I>
- class interface_cast_test
- : protected interface_cast_base<I, noaddref_release<I>,
- ignore_interface_cast_exception>
- {
- . . .
- operator bool () const
- {
- return NULL != parent_class_type::get_pointer();
- }
- . . .
interface_cast_addref同样使用了"惰性异常"策略,尽管只是作为一个缺省的模板策略参数。不同的是,interface_cast_addref使用了addref_release仿函数来对引用计数进行必要的递增。它还提供了一个隐式转换操作符,以便让用户访问他们请求的接口。
程序清单19.17
- template< typename I
- , typename X = ignore_interface_cast_exception
- >
- class interface_cast_addref
- : protected interface_cast_base<I, addref_release<I>, X>
- {
- . . .
- operator pointer_type () const
- {
- return parent_class_type::get_pointer();
- }
- . . .