17

可能重复:
如何为我的班级提供交换功能?

每次我认为我理解它时,我都会看到一些让我非常困惑的东西。

如果你想swap为你自己的类提供一个实现,你会怎么做?

可能性列表是:

  1. 在命名空间内定义一个std(带两个参数),它调用下面的#3
    (有人说这是正确的;有人说是非法的)

  2. 在类中定义一个静态方法,使用两个参数进行交换(对我来说比 #4 更有意义,但我不明白为什么没有人这样做),它根据需要
    调用任何基类swap

  3. 在类中定义一个实例方法,将另一个参数与 交换swap,它根据需要调用任何基类

  4. 在类中定义一个实例方法,将另外两个参数传递给 swap,同样在这里swap,它根据需要调用任何基类

  5. 在你自己的命名空间中定义一个(带两个参数),它调用#3

  6. 别的东西

我自己的理解是我需要#5和#3,然后调用者会swap像这样调用using std::swap; swap(a, b);,但似乎没有人建议这种组合真的让我感到困惑。而且我真的完全不明白#4,因为实际上每个人都在使用实例成员,而实际上操作是静态的。我不知道我的理解是错误的还是我在查找时看到的一堆答案。

正确的方法是什么?

4

3 回答 3

8

根据您自己的理解,我看到的一个常见模式是提供 3 和 5。

  1. 向命名空间添加一个特殊化std::,这是允许的,但可能并非在所有情况下都是可能的(如果您的类型是模板本身)。
  2. 根本没有任何优势,并且在成员之一之外使用时强制使用类型进行限定,这意味着要swap在将您的类型作为成员的其他类型上实现,他们将需要限定调用 ( void swap( other& l, other& r ) { T::swap( l.t, r.t ); })
  3. 不需要友谊,允许与rvalues 一起使用(即使在 C++03 中),并且在某些情况下习惯于std::vector<int>().swap( v );清除向量的内容。
  4. 什么?你误解了代码!这不是声明一个接受两个参数的成员,而是一个接受两个参数的自由函数,并内联定义函数。这相当于 5(没有转发到 3,而是在 free 函数中实现所有内容)。
  5. 同一命名空间中的自由函数允许 ADL 找到它,并允许其他代码使用通用模式,void swap( other& l, other& r ) { using std::swap; swap( l.t, r.t ); }而不必知道类型是否other::t具有特定的swap重载或std::需要使用的重载。转发到 3 允许您提供可以通过 ADL 以及临时对象使用的单个(实际)实现。
于 2012-07-24T16:30:28.380 回答
8

C++11 标准规定,如果和是有效表达式,则对象t可与对象交换,这些表达式选择从包含两个模板的重载集中调用的非成员函数以及由 Argument Dependent Lookup 找到的任何重载,这样的值并被交换。uswap(t, u)swap(u, t)swapstd::swap<utility>tu

在上述条件下交换两个对象的常规方法是:

using std::swap;
swap(t, u);

这里名称查找将考虑ADL 找到的std::swap任何重载swap,然后重载解析将选择最佳匹配。

考虑您的每个实现:

  1. 将重载添加到 namespace 是不合法的std,并且上面的规则并不要求无论如何都要找到它们。std::swap如果特化是针对用户定义的类型(即不是标准库类型或基本类型),则特化标准函数模板是合法的。如果你std::swap特化它是有效的,否则不是,你不能部分特化函数模板.

  2. 将找不到静态成员函数,swap(t, u)因为它需要限定,例如Foo::swap(t, u)

  3. 一元swap成员函数不能被称为swap(t, u),它必须被称为t.swap(u)

  4. 您提供的链接显示了一个非成员 friend函数,ADL 可以找到该函数,因此可用于使类型可交换。

  5. ADL可以找到这样的功能。

所以 2 和 3 不会使类型可交换。4个,5个。如果正确完成,1 可能会这样做,但不能用于交换类模板。

3 不是必需的,但可用于帮助实现 4 或 5,因为成员函数将有权访问类型的内部详细信息,因此可以交换私有成员。对于 4,该功能是朋友,因此已经可以访问。因此,通过消除过程,您需要4 或35。通常认为最好提供成员交换 (3),然后在调用成员交换的同一命名空间 (5) 中提供非成员函数。

于 2012-07-24T20:52:41.253 回答
4

正如您所说,您需要 #5(与您的类型在同一命名空间中的函数)来支持惯用的using std::swap; swap(a,b);. 这样,您的重载将通过依赖于参数的查找优先于std::swap.

如果您的实现swap需要访问类型的私有,那么您将需要调用像 #3 这样的成员函数,或者声明非成员函数 a friend。这就是您在#4 中的示例所做的:friend可以在类中声明和定义函数,但这不会使其成为成员;它仍然在周围的命名空间内。

所以这:

class thing {
    friend void swap(thing & a, thing & b) {/*whatever*/}
};

相当于这个(或多或少 - 见评论):

class thing {
    friend void swap(thing & a, thing & b);
};

inline void swap(thing & a, thing & b) {/*whatever*/}

#1(专门std::swap针对您的类型)是允许的,但有些人会认为将所有内容保存在您自己的命名空间中会更干净。

#2 和 #3 都不允许不合格swap(a,b)的人找到您的实现。

于 2012-07-24T16:30:05.660 回答