公众朋友交换成员函数

在复制和交换成语的美丽答案有一段代码,我需要一点帮助:

class dumb_array { public: // ... friend void swap(dumb_array& first, dumb_array& second) // nothrow { using std::swap; swap(first.mSize, second.mSize); swap(first.mArray, second.mArray); } // ... }; 

他又加了一张纸条

还有其他的说法,我们应该专门为我们的typesstd :: swap,提供一个在一个自由function交换,一个类交换,但这是不必要的:任何正确使用交换将通过一个非限定的调用,我们的function将通过ADLfind。 一个function就可以了。

friend我有点“不友好”的说法,我必须承认。 所以,我的主要问题是:

  • 看起来像一个免费的function ,但它的内部类体呢?
  • 为什么不是这个swap静态 ? 它显然不使用任何成员variables。
  • “任何适当的交换使用将通过ADLfind交换” ? ADL将search命名空间,对吗? 但是,它也看内部类? 或者在这里friend进来?

副题:

  • 用C ++ 11,我应该用noexcept标记我的swap吗?
  • 用C ++ 11和它的范围 ,我应该把friend iter begin()放在friend iter begin()friend iter end() ? 我觉得这里不需要friend ,对吧?

有几种写swap ,比其他的更好一些。 但是,随着时间的推移,发现一个单一的定义效果最好。 让我们考虑一下如何考虑编写一个swap函数。


我们首先看到像std::vector<>这样的容器有一个单参数成员函数swap ,比如:

 struct vector { void swap(vector&) { /* swap members */ } }; 

当然,我们class也应该,对吧? 那么,不是真的。 标准库有各种不必要的东西 ,而成员swap就是其中之一。 为什么? 我们继续。


我们应该做的是确定什么是经典的,以及我们的class级需要做什么来处理它。 而交换的规范方法是使用std::swap 。 这就是为什么成员函数没有用:它们不是我们应该如何交换的东西,一般来说,对std::swap的行为没有影响。

那么,为了使std::swap工作,我们应该提供(和std::vector<>应该提供) std::swap的专门化,对吧?

 namespace std { template <> // important! specialization in std is OK, overloading is UB void swap(myclass&, myclass&) { // swap } } 

那么这肯定会在这种情况下工作,但它有一个明显的问题:function专业化不能是局部的。 也就是说,我们不能用这个专门化模板类,只有特定的实例:

 namespace std { template <typename T> void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization { // swap } } 

这种方法在某些时候有效,但不是全部。 一定会有更好的办法。


有! 我们可以使用friendfunction,并通过ADLfind它:

 namespace xyz { struct myclass { friend void swap(myclass&, myclass&); }; } 

当我们想交换一些东西的时候,我们把 std::swap关联起来,然后进行一个不合格的调用:

 using std::swap; // allow use of std::swap... swap(x, y); // ...but select overloads, first // that is, if swap(x, y) finds a better match, via ADL, it // will use that instead; otherwise it falls back to std::swap 

什么是friendfunction? 这个地区有混乱。

在C ++被标准化之前, friend函数做了一个叫做“朋友名注入”的东西,代码的行为就好像函数是否写在了周围的名字空间中一样。 例如,这些是等同的标准:

 struct foo { friend void bar() { // baz } }; // turned into, pre-standard: struct foo { friend void bar(); }; void bar() { // baz } 

但是,当ADL被发明时,这被删除。 friend函数只能通过ADLfind; 如果你想要它作为一个自由的function,它需要被声明如此(例如见这个 )。 但是! 有一个问题。

如果你只是使用std::swap(x, y) ,那么你的重载将永远不会被find,因为你已经明确地说过“在std查找,而在其他地方”! 这就是为什么有些人build议编写两个函数:一个是通过ADLfind的函数,另一个是处理明确的std:: qualifications。

但就像我们所看到的那样,这不可能在所有情况下都起作用,而且我们最终会变得丑陋。 相反,习惯交换是另外一条路线:不是将类作为提供std::swap ,而是std::swap者的工作,以确保它们不使用像上面那样的合格swap 。 而且,只要人们知道这一点,这个工作就会很顺利。 但问题在于:需要使用不合格的电话是不直观的。

为了方便起见,一些类似Boost的库提供了boost::swap函数,它只是一个非限定的swap调用, std::swap作为一个关联的名字空间。 这有助于使事情简洁一些,但它仍然是一个无赖。

请注意,C ++ 11对std::swap的行为没有任何改变,我和其他人错误地认为是这种情况。 如果你有点这个, 请阅读这里 。


总之:会员function只是噪音,专业化是丑陋的,不完整的,但friendfunction是完整的,有效的。 而当你交换,要么使用boost::swap或与std::swap关联的非限定swap


†非正式地,如果在函数调用期间将会考虑一个名字,则这个名字是相关联的 。 有关详细信息,请阅读第3.4.2节。 在这种情况下,通常不考虑std::swap ; 但是我们可以关联起来(把它添加到由不合格的swap考虑的重载集合中),从而可以find它。

该代码相当于( 几乎在任何情况下):

 class dumb_array { public: // ... friend void swap(dumb_array& first, dumb_array& second); // ... }; inline void swap(dumb_array& first, dumb_array& second) // nothrow { using std::swap; swap(first.mSize, second.mSize); swap(first.mArray, second.mArray); } 

一个类中定义的朋友函数是:

  • 放置在封闭的命名空间中
  • 自动inline
  • 能够在没有进一步资格的情况下引用该课程的静态成员

确切的规则在[class.friend]部分(我引用C ++ 0x草案的第6和7段):

函数可以定义在一个类的朋友声明中,当且仅当该类是一个非本地类(9.8),函数名是不合格的,函数具有命名空间范围。

这样的function隐式内联。 在一个类中定义的朋友函数在它定义的类的(词法)范围内。 在类外定义的朋友函数不是。