19.3 适合使用C强制的场合
尽管我们曾听到关于C风格强制的种种告诫,然而在某些场合下C风格强制的确是有用的,只不过这种场合非常罕见,懒散而马虎的编码风格当然不算,我能想到的只有两种合法的运用。
第一种情况是当代码既需要在C编译器上又需要在C++(www.cppentry.com)编译器上编译时,这种情况下C强制是惟一的选择。出现这种情况可能是因为该强制被用在一个宏当中,或者因包含而被引入源文件中, 后一种情况下,它们或者作为内联函数(参见第12章),或者具有内部连接(见6.4节和11.1节)。
在实际中,由于我们总是应该尽可能地获取更大的威力,所以Synesis库中就包含了一组用于强制的宏:SyCastStatic、SyCastConst、SyCastVolatile、SyCastDynamic、SyCastRaw以及SyCastC,这些宏在C编译器上会展开成原始老式的C强制,而到了C++(www.cppentry.com)编译器下就摇身一变成了相应的C++(www.cppentry.com)强制。这些宏看起来虽丑陋,但确实行之有效,此外,这种方案已经"抓获"关于强制的不计其数的不恰当使用。
C风格强制的第二个用处较为微妙。由于C++(www.cppentry.com)风格的强制很注重源类型和目标类型之间的关系,所以在模板代码中使用它可能惹来麻烦。例如,虽然reinterpret_cast是C++(www.cppentry.com)中的4大强制之一,然而如果转换需要的是一个const或volatile上的改变,interpret_cast就不能胜任了。以下代码展示了这一点:
程序清单19.2
- template< typename T1
- , typename T2
- >
- struct test_cast
- {
- test_cast(T2 *p2)
- {
- m_p1 = static_cast<T1*>(p2); // a
- m_p1 = reinterpret_cast<T1*>(p2); // b
- m_p1 = const_cast<T1*>(p2); // c
- m_p1 = (T1*)p2; // d
- }
- operator T1 *()
- {
- return m_p1;
- }
- // 成员
- protected:
- T1 *m_p1;
- };
-
- int main()
- {
- test_cast<int , int > tc1(NULL); // 1
- test_cast<int , short > tc2(NULL); // 2
- test_cast<int , int const > tc3(NULL); // 3
-
- return 0;
- }
在第一种情景下(从int*到int*的强制),所有C++(www.cppentry.com)强制都能正常运作。对于第二种情况(从int*到short*的强制)来说,只有reinterpret_cast和C风格强制能够胜任。最后一种情况(从int*到int const*的强制)只有const_cast和C风格强制可以工作。虽说在某些场合下,推导出两个类型所对应的const性质并非不可能,而且只要推导出了我们就可以决定调用const_cast还是interpret_cast,然而这种做法要求一定程度的模板技巧,这些技巧在某些编译器上恰恰又是不被支持的。所以,要想保证可移植性(如果对强制我们也考虑可移植性的话),答案可能就是使用C风格强制(当然,我假定你对C风格强制的坏处了如指掌,并且把避免使用它作为一个日常守则,因为使用C风格强制需要技术上的成熟以及钢丝般的神经。不过,我认为在拥护规则之前应该先全面地了解它,即便是那些极好的规则)。
除了以上这些场合之外,其他任何场合下C风格强制都应该被贴上"慎用"标签。实际中的问题则是它们在现存代码中的分布是如此广泛,以至于非常难以根除,因为它们渗透在代码的每个角落,与周围的代码混在一块,难以区分。所以说C++(www.cppentry.com)的4种强制之间的区分是如此明确(有人称它们是丑陋的[Stro1994])并不是一种偶然。幸运的是,有些编译器在这方面提供了一点帮助,GCC提供的是-Wold-style-cast选项,它会对每个C强制给出警告。Digital Mars和Comeau编译器的最近版本则分别通过-wc和-C_style_cast_warning选项提供了同样的功能。
为Comeau(4.3.3或以后的版本)、Digital Mars(8.29或以后的版本)或GCC编译器指定相应的编译选项,会令它们对代码中任何使用C风格强制的地方给出警告,这又是一个需要使用多款编译器工作的理由。