196

copy-and-swap-idiom的漂亮答案中,有一段代码我需要一些帮助:

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);
    }
    // ...
};

他加了一条注释

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

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

  • 看起来像一个自由函数,但它在类体内?
  • 为什么这不是swap静态的?它显然不使用任何成员变量。
  • “任何正确使用交换都会通过 ADL 找到交换”?ADL 将搜索命名空间,对吗?但它是否也在类内部?还是从这里friend进来的?

附带问题:

  • 使用 C++11,我应该用 s 标记我swap的 snoexcept吗?
  • 使用 C++11 及其range-for,我应该在类friend iter begin()friend iter end()以相同的方式放置吗?我认为friend这里不需要,对吧?
4

2 回答 2

198

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


我们首先看到容器 likestd::vector<>有一个单参数的成员函数swap,例如:

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

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


我们应该做的是确定什么是规范的,以及我们的班级需要做什么才能使用它。交换的规范方法是 with 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
    }
}

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

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

这种方法在某些时候有效,但并非所有时候都有效。肯定有更好的办法。


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

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

当我们想交换一些东西时,我们关联†</sup> 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

什么是friend函数?这个地区有混乱。

在 C++ 标准化之前,friend函数会执行一种称为“朋友名称注入”的操作,其中代码的行为就像函数是在周围的命名空间中编写的一样。例如,这些是等效的预标准:

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

// turned into, pre-standard:    

struct foo
{
    friend void bar();
};

void bar()
{
    // baz
}

然而,当ADL被发明时,它被删除了。friend然后只能通过 ADL 找到该函数;如果您希望它是一个自由函数,则需要将其声明为这样(例如,参见 this)。但是看!有问题。

如果你只使用std::swap(x, y),你的重载将永远不会被发现,因为你已经明确说过“看看std,别无他处”!这就是为什么有些人建议编写两个函数:一个作为通过ADL找到的函数,另一个用于处理显式std::限定。

但就像我们看到的那样,这不能在所有情况下都有效,我们最终会陷入丑陋的混乱局面。相反,惯用的交换走的是另一条路:不是让类的工作来提供std::swap,而是交换者的工作是确保他们不使用qualified swap,就像上面一样。只要人们知道,这往往效果很好。但问题就在这里:需要使用不合格的调用是不直观的!

为了使这更容易,像 Boost 这样的一些库提供了函数boost::swap,它只是对 进行非限定调用swap,并std::swap作为关联的命名空间。这有助于使事情再次简洁,但它仍然是一个无赖。

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


简而言之:成员函数只是噪音,特化丑陋且不完整,但friend功能完整且有效。而当你交换时,要么使用boost::swap或不合格swap的 withstd::swap关联。


†非正式地,如果在函数调用期间将考虑名称,则该名称是关联的。有关详细信息,请阅读第 3.4.2 节。在这种情况下,std::swap通常不考虑;但我们可以关联它(将其添加到 unqualified 考虑的重载集合中swap),以便找到它。

于 2011-04-17T19:24:24.227 回答
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);
}

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

  • 放置在封闭的命名空间中
  • 自动地inline
  • 无需进一步限定即可引用类的静态成员

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

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

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

于 2011-04-17T18:41:20.073 回答