19.9 boost::polymorphic_cast
通常情况下强制并不一定得像接口强制那样复杂。事实上,通常我们遇到的强制与接口强制相比要简单得多。
在[Stro1997]中,Bjarne Stroustrup给读者出了一道练习题,让读者去实现一个模板形式的ptr_cast,要求是它必须像dynamic_cast那样工作,但与dynamic_cast不同的是,如果转换失败的话,它对指针和引用类型同样都可能抛出bad_cast异常。Bjarne Stroustrup实际上等于是在说"编写一个模板ptr_cast,它像dynamic_cast那样工作,不同的是,如果转换失败,它会抛出bad_cast异常,而不是返回0。"除了其名字"ptr_cast"之外,如果注意一下它的其他方面,你同样会发现,ptr_cast只能对指针进行强制,不过毫无疑问这正是对它通常被采用的含义的正确解释。Boost中的polymorphic_cast的实现如下:
- template <class Target, class Source>
- inline Target polymorphic_cast(Source* x)
- {
- Target tmp = dynamic_cast<Target>(x);
- if ( tmp == 0 ) throw std::bad_cast();
- return tmp;
- }
它的使用方式如下:- try
- {
- Base *b = new Base();
- Derived *d = boost::polymorphic_cast<Derived*>(a);
- }
- catch(std::bad_cast &x)
- {
- . . .
- }
polymorphic_cast将它的形参x声明为一个指针的做法(即x的类型是Source*,而并非Source或Source&)乍一看比较奇怪,毕竟编译器能够推导出参数类型。然而实际上这样做是有目的的,因为一旦将x显式声明为指针,该强制就被限制为不能被用在除指针之外的其他类型上。
如果我们采取一个更为严格的解释,也就是说,ptr_cast既可以被用在引用上又可以被用在指针上,两种情况下,当转换失败时,它都会抛出bad_cast异常,这样一来,事情又会发生怎样的变化呢?我在网上快速地搜索了一遍,结果只发现非常少的ptr_cast,因此我猜测这个问题肯定非常棘手。对此我所能提出的最好的解决方案是在一个早晨--经过了非常艰苦的思考之后--才想出来的,如程序清单19.20所示:
程序清单19.20
- template <typename T>
- struct ptr_cast
- {
- public:
- typedef typename base_type_traits<T>::cv_type cv_type;
- typedef cv_type &reference;
- typedef cv_type *pointer;
- public:
- template <typename Source>
- ptr_cast(Source &s)
- : m_p(&dynamic_cast<Target>(s))
- {
- // 什么也不做,因为dynamic_cast对引用类型转换失败会抛出异常
- }
- template <typename Source>
- ptr_cast(Source *s)
- : m_p(dynamic_cast<Target>(s))
- {
- if(NULL == m_p)
- {
- throw std::bad_cast();
- }
- }
- ptr_cast(pointer_type pt)
- : m_p(pt)
- {}
- ptr_cast(reference_type t)
- : m_p(&t)
- {}
- public:
- operator reference () const
- {
- return const_cast<reference>(*m_p);
- }
- operator pointer () const
- {
- return const_cast<pointer>(m_p);
- }
- protected:
- pointer m_p;
- };
ptr_cast使用base_type_traits模板(见19.7节)来推导出恰当的带cv限定的基类型(base type)。该类型然后被用来合成对应的指针类型和引用类型,而后两者又被用来定义两个对应的隐式转换操作符,它们负责将转换后的值返回给调用方。这个问题的最后一个环节是,dynamic_cast用在指针身上的返回值会被测试是否为NULL,如果dynamic_cast转换失败从而返回的是NULL的话,就会导致抛出一个bad_cast异常。
现在,我们可以将它用于指针和引用上了。
- class X
- {};
- class D
- {};
- D d;
- dynamic_cast<X&>(d); // 抛出bad_cast
- ptr_cast<X&>(d); // 抛出bad_cast
- dynamic_cast<X*>(&d); // 返回NULL
- ptr_cast<X*>(&d); // 抛出bad_cast
该实现只在那些支持模板局部特化的编译器上可以工作,这样,Visual C++(www.cppentry.com)(6.0和7.0)以及Watcom就立即出局了,因为它们都不支持局部特化。实际上的实现针对Borland作了一些迂回处理,另外,在GCC上,如果向它传递一个指向中间临时变量而不是单独的变量的指针,GCC就会给出一个二义性错误。但是在CodeWarrior、Comeau、Digital Mars、Intel以及Visual C++(www.cppentry.com)7.1上,所有情况下工作得都同样出色。
当然,如果可能的话,使用一个统一的方式去处理指针和引用是个更为诱人的想法,但考虑到这个版本的实现的"不彻底性",我们就不难想象为什么Boost中的实现要将其自身限制在只能处理指针的前提下了。