公共交换成员函数

tow*_*owi 154 c++ friend copy-and-swap c++11

复制和交换习语的漂亮答案中,有一段代码我需要一些帮助:

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);
    }
    // ...
};
Run Code Online (Sandbox Code Playgroud)

他补充说明

还有其他声称我们应该专门为我们的类型使用std :: swap,提供一个类内交换以及一个自由函数交换等等.但这都是不必要的:任何正确使用swap都将通过一个不合格的调用,我们的功能将通过ADL找到.一个功能就可以了.

随着friend我对"不友好"的条款一点,我必须承认.所以,我的主要问题是:

  • 看起来像一个自由函数,但它在类体内?
  • 为什么这不是swap静态的?它显然不使用任何成员变量.
  • "任何正确使用交换都会发现通过ADL进行交换"?ADL会搜索命名空间,对吧?但是它也看到了课程内部吗?或者在这里friend进来?

副题:

  • 随着C++ 11,我要记住我的swaps的noexcept
  • 随着C++ 11和它的范围,对,我应该将friend iter begin()friend iter end()在类中以同样的方式?我觉得friend这里不需要,对吗?

GMa*_*ckG 161

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


我们首先看到容器就像std::vector<>具有单参数成员函数swap,例如:

struct vector
{
    void swap(vector&) { /* swap members */ }
};
Run Code Online (Sandbox Code Playgroud)

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


我们应该做的是确定什么是规范的,以及我们的课程需要做些什么才能使用它.规范的交换方法就是用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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

这种方法在某些时候有效,但不是所有时间都有效.肯定有更好的办法.


有!我们可以使用一个friend函数,并通过ADL找到它:

namespace xyz
{
    struct myclass
    {
        friend void swap(myclass&, myclass&);
    };
}
Run Code Online (Sandbox Code Playgroud)

当我们要交换某些内容时,我们会关联 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
Run Code Online (Sandbox Code Playgroud)

什么是friend功能?这个地区有混乱.

在C++标准化之前,friend函数执行了一个名为"朋友名称注入"的操作,其中代码的行为就好像函数是在周围的命名空间中编写的一样.例如,这些是相同的预标准:

struct foo
{
    friend void bar()
    {
        // baz
    }
};

// turned into, pre-standard:    

struct foo
{
    friend void bar();
};

void bar()
{
    // baz
}
Run Code Online (Sandbox Code Playgroud)

然而,当发明ADL时,这被删除了.friend然后只能通过ADL找到该功能; 如果你想将它作为一个自由函数,它需要被声明为(例如,见这个).但是,瞧!有一个问题.

如果你只是使用std::swap(x, y),你的过载将永远不会被发现,因为你已经明确地说"看看std,而不是其他地方"!这就是为什么有些人建议写两个函数:一个是通过ADL找到的函数,另一个是处理显式std::资格.

但就像我们所看到的那样,这在所有情况下都无法奏效,而且最终会出现一个丑陋的混乱局面.相反,惯用交换是另一条路线:不是让它成为班级提供的工作std::swap,而是交换者的工作,以确保他们不使用合格的swap,如上所述.只要人们了解它,这往往效果很好.但其中存在的问题是:需要使用不合格的电话是不直观的!

为了更方便,像加速一些库提供的功能boost::swap,这只是做一个不合格的呼叫swap,以std::swap作为相关的命名空间.这有助于使事情再次简洁,但它仍然是一个无赖.

请注意,C++ 11中的行为没有变化std::swap,我和其他人错误地认为是这种情况.如果您对此感到满意,请阅读此处.


简而言之:成员函数只是噪声,专业化是丑陋和不完整的,但friend功能是完整和有效的.当你交换时,要么使用,要么使用boost::swap不合格swapstd::swap关联.


†非正式地,如果在函数调用期间考虑名称,则关联名称.有关详细信息,请参阅§3.4.2.在这种情况下,std::swap通常不予考虑; 但是我们可以将它关联起来(将它添加到不合格所考虑的重载集swap),允许它被找到.

  • 我不同意成员函数只是噪音.成员函数允许例如`std :: vector <std :: string>().swap(someVecWithData);`,这对于`swap`自由函数是不可能的,因为两个参数都是通过非const引用传递的. (8认同)
  • @GMan:如果一个是以另一个实现的,则DRY原则不适用.否则,没有人会提倡一个带有'operator =`,`operator +`和`operator + =`的实现的类,但很明显,相关类的运算符被接受/预期存在对称性.在我看来,成员`swap` + namespace-scoped`exchange`也是如此. (4认同)
  • @ildjarn:你可以在两行上完成.拥有成员职能违反了DRY原则. (3认同)
  • @GMan我认为它正在考虑太多的功能.鲜为人知,但即使是`function <void(A*)> f; if(!f){}`只能因为`A`声明一个`运算符!'接受`f`和`f`自己的'运算符!'(不太可能,但可能发生).如果`function <>`的作者认为"哦,我有'操作员bool',我为什么要实现'操作员!'?那会违反DRY!",这将是致命的.你只需要为`A`实现一个`operator!`,为`function <...>`设置一个`A`,事情就会破坏,因为两个候选都需要用户定义的转换. (3认同)
  • 很棒的答案!也许是常见问题解答的最佳候选人?:) (2认同)

Ben*_*igt 7

该代码相当于(几乎在所有方面):

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);
}
Run Code Online (Sandbox Code Playgroud)

在类中定义的友元函数是:

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

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

当且仅当该类是非本地类(9.8),函数名称是非限定的,并且该函数具有命名空间范围时,才能在类的友元声明中定义函数.

这样的功能是隐含的内联.在类中定义的友元函数位于定义它的类的(词法)范围内.在课外定义的朋友函数不是.

  • 实际上,在标准C++中,友元函数不会放在封闭的命名空间中.旧的行为被称为"朋友名称注入",但被ADL取代,在第一个标准中被替换.请参阅[this]的顶部(http://womble.decadent.org.uk/c++/syntax-errors.html).(但行为非常相似.) (2认同)
  • 并不完全等同。问题中的代码使得“swap”仅对 ADL 可见。它是封闭命名空间的成员,但其名称对于其他名称查找表单不可见。编辑:我发现 @GMan 又更快了 :) @Ben 在 ISO C++ 中一直都是这样 :) (2认同)
  • @Ben:不,朋友注入从来没有存在于标准中,但之前广泛使用的是为什么这个想法(和编译器支持)倾向于继续,但它在技术上并不存在.`friend`函数只能由ADL找到,如果它们只需要具有`friend`访问权限的自由函数,它们都需要在类中声明为`friend`,并且作为类外的正常自由函数声明.例如,您可以在[此答案](http://stackoverflow.com/questions/4027604/x/4027734#4027734)中看到这种必要性. (2认同)
  • @towi:因为友元函数在命名空间范围内,所以您所有三个问题的答案都应该很清楚:(1)它是一个免费函数,并且具有对类的私有和受保护成员的友元访问权限。(2) 它根本不是成员,既不是实例也不是静态的。(3) ADL 不在类内搜索,但这没关系,因为朋友函数具有命名空间范围。 (2认同)