19.7 union_cast(2)
第三个约束是基于这样一种考虑:union_cast不应该允许它的源类型或目标类型为指向类类型的指针,因为这会令用户能够在类继承体系中进行不加检查的向下或横向强制,这两者应该由dynamic_cast来进行,dynamic_cast会进行必要的运行期检查。我们通过在union_cast中放置针对强制的源类型及目标类型的约束来确保这一点。换句话说,除了确保被操纵的类型是POD类型之外,我们还得确保任何指针类型(当然它们也是POD类型,见序言)都是指向POD类型(或void)的。我们通过两个步骤来实现这一点:首先通过base_type_traits来取出指针的基类型(及指针所指的类型),然后对该基类型施加constraint_must_be_pod_or_void()约束(见1.2.4节)。
对于支持局部特化的编译器来说,base_type_traits模板能够推导出任何类型的基类型。它是一个带有几个恰当的(偏)特化版本的简单模板,如下:
程序清单19.12
- template <typename T>
- struct base_type_traits
- {
- enum { is_pointer = 0 };
- enum { is_reference = 0 };
- enum { is_const = 0 };
- enum { is_volatile = 0 };
- typedef T base_type;
- typedef T cv_type;
- };
- . . . // 各种cv/ptr/ref特化
- template <typename T>
- struct base_type_traits<T const volatile *>
- {
- enum { is_pointer = 1 };
- enum { is_reference = 0 };
- enum { is_const = 1 };
- enum { is_volatile = 1 };
- typedef T base_type;
- typedef T const volatile cv_type;
- };
到目前为止,我们已经对union_cast引发的问题中的大部分进行了处理,而且任何可能的误用都会在编译期得到处理。最后一个问题在于,union_cast可能被用来产生一个"对齐有误"的指针(reinterpret_cast同样可能存在这种情况)。这自然不能在编译期检查出来,但是这里我们同样可以求助于base_type_traits。在我的union_cast实现中有一个断言,它负责对由非指针向指针转换的情况进行测试,其作用是:确保被转换的源类型实例的值,是以目标(指针)类型的基类型的大小为边界进行对齐的。换句话说,如果目标类型是uint64_t (const) (volatile)* 的话,那么源类型实例就必须是一个值为8的倍数的整型。 如果这个要求不能满足,我的做法是触发(运行期)断言,当然你在自己的实现中也可以选择抛出一个异常。
将上面讲述的4种措施放在一起,我们就可以说,union_cast比reinterpret_cast更安全,而在可用性方面的惟一代价就是不管是其源类型还是目标类型都不能为指向类类型的指针。因为这样做会将C++(www.cppentry.com)程序员置于最危险的境地。union_cast负责进行(并验证)良性的转换,而将那些危险的转换留给程序员去明确地写出来,我认为这是union_cast的一个好处。
注意,union_cast并不阻止你从--譬如说char const* --转换至wchar_t*,因为这正是我们想要封装的那些需要多步的转换之一。这意味着危险性,因此我给出了严格的律条:只能通过typedef来使用union_cast,例如WPARAM2HDROP,因为我们可以合理地假定人们创建和使用这些typedef之前是经过了反复斟酌的。