公共交换成员函数

时间:2011-04-17 18:35:03

标签: c++ c++11 friend copy-and-swap

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,提供一个类内交换以及一个自由函数交换等等。但这都是不必要的:任何正确使用swap都将通过一个不合格的电话,我们的功能将通过ADL找到。一个功能就可以了。

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

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

侧面的问题:

  • 使用C ++ 11,我应该使用 swap 标记我的noexcept吗?
  • 使用C ++ 11及其范围,我应该在课程中以同样的方式放置friend iter begin()friend iter end()吗?我认为这里不需要friend,对吗?

2 个答案:

答案 0 :(得分:149)

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


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

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

当然,我们的班级也应该,对吧?好吧,不是真的。标准库有all sorts of unnecessary things,成员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
    }
}

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

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&);
    };
}

当我们要交换某些东西时,我们将 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找到;如果你想把它作为一个自由函数,它需要被声明为(例如see this)。但是,瞧!有一个问题。

如果您只是使用std::swap(x, y),那么您的重载将从不,因为您已明确说“查看std,而不是其他地方”!这就是为什么有些人建议编写两个函数:一个是通过ADL找到的函数,另一个是处理显式std::资格。

但是就像我们看到的那样,这在所有情况下都无法奏效,我们最终会陷入一团糟。相反,惯用交换是另一条路线:而不是让类提供std::swap的工作,交换器的工作是确保他们不使用合格的swap,如上所述。只要人们了解它,这往往效果很好。但其中存在的问题是:需要使用不合格的电话是不直观的!

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

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


简而言之:成员函数只是噪声,专业化是丑陋和不完整的,但friend函数是完整的并且有效。当您进行互换时,请使用boost::swap或与swap关联的不合格std::swap


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

答案 1 :(得分: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),函数名称是非限定的,并且该函数具有命名空间范围时,才能在类的友元声明中定义函数。

     

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