CUJ : Sutter's Mill : Befriending Templates[1]

[入库:2005年8月18日] [更新:2007年3月24日]

本文简介:选择自 taodm 的 blog

让模板成为友元
作者:herb sutter
出处:cuj:sutter’s mill
--------------------------------------------------------------------------------

假设我们有一个函数模板,对它所操作的对象执行somethingprivate()。 特别地,考虑一下

boost::checked_delete() 函数模板,它delete传给它的对象-- 在此过程中,它调用对象的析构函数:
   namespace boost {
     template<typename t> void checked_delete( t* x ) {
       // ... other stuff ...
       delete x;
     }
   }

现在,假定你想将这个函数模板作用于一个类,问题是,你所需要的操作(这里是析构函数) 碰巧是私有

的:
    // example 1: no friends
   //
   class test {
     ~test() { } // private!
   };

   test* t = new test;
   boost::checked_delete( t ); // error: test's destructor is private,
                               // so checked_delete can't call it.

解决方法非常简单:只要让checked_delete() 成为test的友元。
(唯一的其它选择是放弃[封装],让析构函数成为公有)。

我为此写下这篇文章是因为,唉,让处于另外一个namespace中的模板成为友元,说比做容易多了:
l 坏消息: 有两个完全符合标准的方法可以实现它,但没有一个可在目前所有的编译器上工作。
l 好消息: 其中之一除了gcc以外,在我所尝试的每一目前的编译器上的都能工作。

最初的尝试
--------------------------------------------------------------------------------
这里是最初的代码:
stephan born <stephan.born@bneousspeanm.de> 写为:
   // example 2: one way to grant friendship (?)
   //
   class test {
     ~test() { }
     friend void boost::checked_delete( test* x );
   };

唉,这个代码不能工作于发贴者的编译器上(vc++6.0)。事实上,它
在很多编译器上的都不行。 要之,例子2中的友元声明:
l 技术上合法,但是依赖于[c++]语言的一个含糊点
l 被许多目前的编译器拒绝,包括一些非常优秀的编译器
l 很容易被修正为不依赖于含糊点,并工作在几乎目前所有的编译器上 (除了gcc)

为什么它是合法的,却又含糊的

当申明友元时,将发生四种情形 (列举于c++标准,clause 14.5.3)。摘要如下:
   当申明友元却没有在其中任何地方使用“template”关键字时:
   1. 如果,友元的名字看起来像带着显式参数的模板特化版本(比如,name<sometype>),
        那么,友元是那个模板的一个显式特化版本
   2. 其次,如果,友元的名字被类名或namespace名 (比如,some::name)限定,并且,类或namespace

中包含一个匹配的非模板函数,
        那么,友元就是那个函数
   3. 再者,如果, 友元的名字被类名或namespace名 (比如,some::name)限定,并且,类或namespace

中包含一个匹配的模板函数(能够推导出恰当的模板参数),
        那么,友元就是那个函数模板的一个特化版本
   4. 最后,那个名字必须是无[类或namespace名]限定的,并且申明(或重复申明)了一
      普通的 (非模板) 函数。

很清楚, #2 和 #4 只匹配于非模板,因此,将模板的特化版本申明为一个友元,我们有两个选择: 写成

满足规则#1,或写成满足规则#3。对应于我们的例子,选择是:
// the original code, legal because it falls into bucket #3
   //
   friend void boost::checked_delete( test* x );

// adding "<test>", legal because it falls into bucket #1
   //
   friend void boost::checked_delete<test>( test* x );

第一个是第二个的简写形式……但是,只有当名字是有限定的( 这里被“boost::”),并且, 没有相匹

配的非模板函数位于同一作用域空间。 这个友元声明规则的含糊点把人弄晕了╟ 对绝大部分当前的编译

器也是如此!一我能找到至少三个理由以要求避免使用它。

为什么要避免规则 #3

有好些理由要求避免规则#3,即使它在技术上是合法的:
1. 规则#3 并不总能工作。
如上所述,它是最初规则的简写形式,但是只在有类名或namespace名限定,并且其中没有相匹配的非模

板函数时,才起作用。
特别地,如果 namespace 有 ( 或稍后又获得) 一个匹配的非模板函数,会改变选择,因为非模板函数的

出现将导致规则#2抢先了规则#3。 有些微妙和令人惊讶,不是吗?很容易犯错,不是吗?让我们避免这

样的微妙。
2. 规则#3真的很锋利,易碎,并让大多数读你的代码的人感到吃惊。
举例来说, 考虑这个非常微小的变体 -- 我所作的所有改变就是去除限定字“boost”:
   // variant: make the name unqualified
   //
   class test {
     ~test() { }
     friend void checked_delete( test* x ); // ouch: legal, but not what you
   };                                       // want. more about this later.

如果你省略 “boost::”(也就是, 如果调用是无限定的),你将掉入一个完全地不同的规则中(规则#4)

,根本不对函数模板进行匹配,这可一点都不好玩。 赌二十块钱,差不多我们这个美丽行星上每一个人

都会同意我的观点:太令人吃惊了,仅仅省略一个 namespace 名字,就如此的大幅地改变了友元声明的

意义。 让我们避开这个锋利的东西吧。

规则#3锋利、易碎,搞昏了大多数编译器

让我们用规则#1和规则#3,在目前的编译器上进行一个大范围的试验,看看它们是怎么理解的。编译器对

标准的理解和我们一致吗(在我们读了上面这么多之后)?至少最牛的编译器会符合我们的期待吧?否,

还是否(no, and no, respectively)。

让我们先试规则#3:
// example 1 again
//
namespace boost {
  template<typename t> void checked_delete( t* x ) {
    // ... other stuff ...
    delete x;
  }
}
class test {
  ~test() { }
  friend void boost::checked_delete( test* x ); // the original code

本文关键:CUJ, Herb Sutter, 模板, 友元
 

本站最佳浏览方式为 分辨率 1024x768 IE 6.0(或更高版本的 IE浏览器)

go top