19.8.3 interface_cast_test
我们将会在第七部分看到,不管你的错误处理是基于异常还是基于返回值,你总免不了要在某个地方对错误作出响应(处理)。所以,倘若我们使用的是interface_cast_addref,我们就必须检查它的返回值是否为NULL,而在使用interface_cast_noaddref的时候,我们则需要捕获bad_interface_cast异常。由于COM是一个二进制接口,异常不能跨连接单元被捕获(见第9章),所以我们可能会在该强制的周围看到许多try-catch块。
COM的一致性规则[Box1998]要求,如果一个实例曾对QueryInterface()调用返回过某个给定的接口,它就必须在它的整个生命期中都具有这种行为。由此我们引出第三个接口强制interface_cast_test。本质上这是一个假装成逻辑垫片(Logical Shim)的强制(见20.3节)。所谓逻辑垫片,即是用在条件表达式中的一种垫片。interface_cast_test可以用来确保某个对应的interface_cast_noaddref不会抛出异常,或者,对于interface_cast_addref来说,则是确保它不会返回NULL。但它真正派上用场的地方其实是跟非临时性的interface_cast_noaddref结合使用的时候,像这样:
- IUnknown *punk = . . .;
- if(interface_cast_test<IThing*>(punk))
- {
- interface_cast_noaddref<IThing*> thing(punk);
- thing->Method1();
- . . .
- thing->MethodN();
- } // thing的析构函数会负责释放它引用的接口
当然,还有另一种做法,即我们创建一个非临时的thing实例,然后如果punk不能被转换为IThing*,我们就捕获它抛出的异常,殊途同归。然而在实际中,COM组件的创建其实是一个轻量级的举动,有时甚至是在没有C/C++(www.cppentry.com)运行时库支持的情况下[Rect1999]。在这样的场合下,interface_cast_noaddref缺省抛出的异常可以(通过预处理指令)设置为实际不抛出异常,从而允许上面的代码既简洁又安全,同时不失其轻量级本色。这在C++(www.cppentry.com)里可能被认为是不"妥当"的做法,但我们是不完美主义的实践者,而在当前(技术上的)环境的限制下这又确实能够给代码质量带来实质性的提升,所以我们选择采用它。
我拥护Kernighan和Pike的意见[Kern1999],相信异常应该仅被用在那些真正意料之外的情况下,因此我在大多数场合下都更倾向于采用这个策略。