2008年(909)
分类:
2008-05-06 21:31:59
假设我们有一个函数模板,可以调用其所操作的对象的SomethingPrivate()方法。特别地,考虑boost::checked_delete()函数模板,它用以删除指定的对象——在它的实现中,会调用该对象的析构函数:
namespace boost { template现在,假设你想要在一个类中使用该函数模板,则该类中只有一个私有的方法(析构函数):void checked_delete( T* x ) { // ... 其它代码 ... delete x; } }
class Test { ~Test() { } // 私有的! }; Test* t = new Test; boost::checked_delete( t ); // 错误: // Test 的析构函数是私有的, // 因此checked_delete不能调用它。解决方案很简单:只要令checked_delete()成为Test的友元即可。(其它的方法都需要Test提供公共的析构函数)如何才能实现这个容易的解决方案呢?事实上, C 标准提供了2种方法来合法又便捷的实现它。
//例1:授权给友元的方法 class Test { ~Test() { } friend void boost::checked_delete( Test* x ); };事实上,上述代码不仅不能通过上边所说的编译器的编译,而且不能通过几乎所有的编译器。简单的说,例1的友元声明:
//源代码,合法,因为它符合#3的形式 friend void boost::checked_delete( Test* x );或者
// 增加了"前者是后者的简化形式...但只有在该名字处于某一作用域(此例为boost::)中,而且其作用域中必须不存在与其匹配的非模板函数。两者都是合法的,但是前者运用了友元声明规则中的晦涩之处,它会令使用它的人感到困惑——对当前的大多数编译器来说!——下边阐述了为何要求避免使用它的三个原因。",合法, // 因为5它符合#1的形式 friend void boost::checked_delete ( Test* x );
// 变化:去掉该名字的限定域, // 这意味着产生了很大的变化。 class Test { ~Test() { } friend void checked_delete( Test* x ); };如果你忽略了boost::(例如,如果该调用是无限定域的),那么你其实是使用了#4,它根本就不包含函数模板,尽管它看起来优雅且简练。我敢和你用打赌买根"老高太太糖葫芦"(译者:donuts,面包圈,不可以随便译么?^_^),我认为我们这个美丽行星上的每个人都会同意我的看法——只忽略了命名空间的名字却如此剧烈的改变了友元声明的含义——这是非常不合理的。让我们必避免这种颠簸的构造吧。
// 再来看看例1 namespace boost { template在你自己的编译器上试试看,比较我们的结果。如果你曾经看过电视节目"家族分歧"(Family Feud),你现在可能会想象得到Richard Dawsond的名言了:"Survey Saaaaays"(译者:横向比较?原文就是那么多个a呀:)(见表1)。void checked_delete( T* x ) { // ... 其它代码 ... delete x; } } class Test { ~Test() { } friend void boost::checked_delete( Test* x ); // 原始代码 }; int main() { boost::checked_delete( new Test ); }
这种情况下,横向比较的结果说明了此语法并没有被现在的编译器所公认。顺便说一句,令我们很惊讶的是Comeau,
EDG, Intel 编译器都承认了这种语法,这是因为它们都是基于EDG C 来实现的。在被测试的5种不同的C 语言实现中,有三种不能支持这个版本(gcc, Metrowerks, Microsoft),另外两种支持(Borland, EDG)。
让我们接着来试试C 标准所支持的另一种方法吧,#1:
// 例2:声明友元的另一个方法 namespace boost { template或者,等价地,我们清晰地声明:void checked_delete( T* x ) { // ... 其它代码 ... delete x; } } class Test { ~Test() { } friend void boost::checked_delete<>( Test* x ); }; int main() { boost::checked_delete( new Test ); }
friend void boost::checked_delete无论哪一种,对上边的编译器测试的横向比较结果说明了它们被支持得更好(见 表2)。( Test* x );
// 例3:如果checked_delete不在一个命名空间中... // 不再在 boost:: 中 template横向比较...(见 表3)。void checked_delete( T* x ) { // ... 其它代码 ... delete x; } class Test { // 不再需要 "boost" friend void checked_delete ( Test* x ); }; int main() { checked_delete( new Test ); }
namespace boost { template上边的友元声明又落入了#4的形式:"4.否则,友元的名字必须不被冠以作用域修饰,而是声明为一个常规函数(非模板)。"这实际上是在全局命名空间中声明了一个新的常规非模板函数::checked_delete(Test*)。void checked_delete( T* x ) { // ... 其它代码 ... delete x; } } using boost::checked_delete; class Test { ~Test() { } // 没有模板特化! friend void checked_delete( Test* x ); };
namespace boost { template上边不是合法的C 代码——C 标准没有明确指出这是合法的。在标准委员会中,曾经有一过一次公开的讨论——以决定该用法是否合法,存在一个观点认为它应该是非法的,因为事实上所有我测试过的当前编译器都拒绝它。为什么人们认为它不能是合法的呢?为了保持一致性,因为using的存在是为了令名字使用起来更加容易——调用函数/在变量或参数声明中使用类型名。声明有所不同的是:正如你必须在模板的原始作用域中声明该模板的一个特化一样,(你不能在另一个命名空间中通过"using"来达到这一目的),你只能将一个模板的特化声明为——冠以该模板作用域的——友元(而不能通过"using"来做到这一点)。void checked_delete( T* x ) { // ... 其它代码 ... delete x; } } using boost::checked_delete; class Test { ~Test() { } friend void checked_delete<>( Test* x ); //合法么? };
// 来自例1 friend void boost::checked_delete ( Test* x ); // 来自例2:增加<>或本文演示了——不像例2所示,写上"<>"或"friend void boost::checked_delete<>( Test* x );
namespace boost { template如果你的编译器不支持这两种声明友元的合法语法的话,你就要把必要的函数声明为公共的了――不过,应该加上一条注释以说明原因,并提醒自己一旦编译器升级了的话,便应尝试将这些函数声明改回成私有的。void checked_delete( T* x ); } class Test { friend void boost::checked_delete ( Test* x ); // 不好 friend void boost::checked_delete<>( Test* x ); // 好 };