19.7 union_cast(1)
在[Stro1997]中,Bjarne Stroustrup提到他发现可以使用联合(union)来实现不相关类型之间的强制转换,即便是对那些不被C++(www.cppentry.com)中的任何内建强制操作符所支持的强制转换,这种手段也是行之有效的。
程序清单19.10
- template< typename TO
- , typename FROM
- >
- union union_cast
- {
- union_cast(FROM from)
- : m_from(from)
- {}
- operator TO () const
- {
- return m_to;
- }
- private:
- FROM m_from;
- TO m_to;
- };
Bjarne说这是"邪恶的"hack手法,并且认为使用它的人要么是天真,要么喜欢冒险。我完全同意这种说法,不过在有些情况下该手段确实能够带来帮助。
Bjarne描述的两个具体的问题是关于大小和对齐的。在有些架构上,指针的大小跟int的大小是不同的,所以它们之间的转换可能会导致危险的截断。同样,有些架构对指针具有特定的对齐要求,在这种架构上,试图对一个对齐有误的指针进行解引用将会导致不愉快的后果,比方说,一个硬件异常。
- long l = 3; // 不一定可以作为string的地址. . .
- string *ps = union_cast<string*, long>(l); // 哎呀!
union_cast并不能提供比reinterpret_cast更多的保证,甚至从某些方面来说更少,因为它能执行一些reinterpret_cast所不能执行的强制。
既然union_cast如此糟糕,那我们干嘛还要考虑使用它呢?原因有两点,都是基于非常实际的考虑。首先,正如19.3节提到的,有些转换可能需要不止一次强制才能完成,这就可能导致难于阅读和维护的冗长代码。其次,有些编译器不仅会对隐式强制和C风格强制给出警告,对其他任何强制的使用同样也会给出警告,这可能会令那些拥护"零警告"哲学的信徒们抓狂。
一些被广泛使用的系统架构完全强迫使用强制。最明显的、第一个蹦入我脑海的就是Windows API。Win32的每个消息都与两个不透明数据相关联,这两个不透明值的类型分别是WPARAM和LPARAM,在Win32上它们实际上分别是uint32_t和sint32_t。它们被用来传递包括系统对象句柄、C字符串的指针以及C++(www.cppentry.com)对象的指针在内的所有东西。因而,在Win32上,定义一些有助于提高可读性和可维护性的组件是相当有用的,同样,在这些场合下,尽可能地引入类型安全性也是有用之举。因此,我们可以对union_cast进行一些特定的参数化,这样做是合适的,例如:
- typedef union_cast<LPARAM, wchar_t const*> StrW2LPARAM;
- typedef union_cast<HDROP, WPARAM> WPARAM2HDROP;
union_cast是如此危险,因而在顺利使用它之前,我们需要采取一些严厉的措施对它的力量加以约束。第一个约束是,它永远不该以原始形式被使用。如果你在我的源码控制系统中grep一下,你就会发现不管是在哪个产品或组件当中都找不到任何一处实现代码使用了union_cast的单个实例。它被使用的惟一地点是在技术/操作系统相关的库的头文件中,被用来进行一些特定的typedef定义。上面代码中展示的StrW2LPARAM和WPARAM2HDROP就是这样的两个typedef。跟那些深藏在实现文件中的单个特定的union_cast实例相比,我们可以认为这些相对醒目的typedef经受过更多的斟酌。
第二个被用来最大限度地降低union_cast的误用风险的措施,是将若干约束直接内置到union_cast类中,如程序清单19.11所示。其中第一个约束是,转换的源类型和目标类型必须具有同样大小,这就免除了截断的危险。我们想要的第二个约束是源类型和目标类型必须为POD类型,不过由于我们正是使用联合(union)来实现该约束的,既然union_cast内部已经使用了联合来存放这两个类型的实例,所以这件工作我们可以省去。但为了明确起见,我们仍然还是在union_cast当中放置了constraint_must_be_pod()(见1.2.4节),尽管这完全没有必要,因为constrain_must_be_pod()的工作方式就是试图去定义一个包含了给定类型的联合。
程序清单19.11
- template< typename TO
- , typename FROM
- >
- union union_cast
- {
- explicit union_cast(FROM from)
- : m_from(from)
- {
- // 1. 源类型和目标类型的大小必须相等
- STATIC_ASSERT(sizeof(FROM) == sizeof(TO));
- // 2. 源类型和目标类型必须都是POD类型
- constraint_must_be_pod(FROM);
- constraint_must_be_pod(TO);
- # if defined(ACMELIB_TEMPLATE_PARTIAL_SPECIALIZATION_SUPPORT)
- // 3. 源类型和目标类型如果是指针的话必须指向POD类型,否则不能是指针类型,
- typedef typename base_type_traits<FROM>::base_type
- from_base_type;
- typedef typename base_type_traits<TO>::base_type
- to_base_type;
- constraint_must_be_pod_or_void(from_base_type);
- constraint_must_be_pod_or_void(to_base_type);
- # endif /* ACMELIB_TEMPLATE_PARTIAL_SPECIALIZATION_SUPPORT */
- }
- . . .
- };