如何实现swap()函数?

18

可能是重复问题:
如何为我的类提供一个交换函数?

每次我以为我明白了,但总会看到一些让我困惑的东西。

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

可能的选择列表如下:

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

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

  3. 在类内部定义一个实例方法,接受另一个参数进行交换, 该方法根据需要调用任何基类的 swap 方法

  4. 在类内部定义一个实例方法,接受两个其他参数进行交换, 同上, 该方法根据需要调用任何基类的 swap 方法

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

  6. 其他内容

我的理解是我需要 #5 和 #3,在这种情况下,调用者将会像这样调用using std::swap; swap(a, b);,但是没有人建议这种组合方式,这真的让我感到困惑。我真的不理解 #4,因为每个人似乎都在使用一个实例成员,而实际上操作是静态的。我无法确定我的理解是否错误,或者当我查找答案时看到的一堆答案是否正确。

什么是正确的方法?


4
@Xeo:在标记我的问题为重复之前,你是否真正“阅读”了我的问题?我发帖的“全部原因”就是这些“重复”让我感到困惑。 - user541686
1
请注意您对第4点的误解,它实际上不是一个实例函数,而是一个“友元”函数,与第5点相同。 - Xeo
@Xeo:哇...你会在这里发表那个答案吗?(编辑:或者...看起来有人比你更快地完成了。)我完全没有意识到它是一个“友元函数”(从未定义过其中之一...不知道它们是静态的)。 - user541686
2
一个友元函数不是静态的:静态成员仍然是类的成员,而友元不是。此外,在C++中,正确的术语是“成员函数”,而不是“实例方法”。 - Jonathan Wakely
1
是的,“free”或“non-member”可以使用,记住C++不是一种面向对象的语言,默认情况下事物不是成员或“实例特定”的。在C++中,“static”是一个非常重载的术语,除非你在技术上使用它,否则最好避免使用这个词来传达你的想法。 - Jonathan Wakely
显示剩余2条评论
3个回答

8
我看到的一个常见模式是按照你自己的理解提供3和5。
1. 将一个专业化添加到 std :: 命名空间中,这是允许的,但在某些情况下可能不可能(如果您的类型本身是模板)。 2. 没有任何优势,并强制在使用成员之外的地方限定类型,这意味着为实现对其他持有您类型的类型进行交换的swap时,它们需要限定调用(void swap(other& l,other& r){T :: swap(l.t,r.t);} ) 3. 不需要友元,可以与rvalue一起使用(即使在C ++ 03中也是如此),并且在某些情况下是惯用的 std :: vector<int>()。swap(v); 清除向量的内容。 4. 什么?您误解了代码!这不是声明一个带两个参数的成员,而是一个带两个参数的自由函数,并内联定义该函数。这等同于5(不转发到3,而是在自由函数中实现所有内容)。 5. 同一命名空间中的自由函数允许ADL找到它,并启用其他代码使用常见模式void swap(other& l,other& r){using std :: swap; swap(l.t,r.t);} ,而无需知道other :: t类型是否具有特定的swap 重载或是否需要使用std :: 中的重载。将其转发到3允许您提供可通过ADL和临时对象使用的单个(真正的)实现。

1
根据#1规定,允许专业化,但不允许重载。此外,你可以为模板类进行专业化处理。 - Mooing Duck
1
@MooingDuck: 你可以针对模板类进行专门化处理。你可以为模板的任何专门化提供swap的专门化,但是你不能提供一种在所有模板实例中通用实现功能的swap的专门化。也就是说,你可以提供void std::swap( mytemplate<int>&, mytemplate<int>& ),但不能提供template <typename T> std::swap( mytemplate<T>&, mytemplate<T>& );,因为它不是标准swap的专门化,而是一个不同的基础模板。 - David Rodríguez - dribeas
1
哦,我猜你是对的,这不会是一个专业化。我错了。 - Mooing Duck

8
C++11标准规定,如果存在非成员函数swap的重载集合,该集合包含<utility>中的两个std::swap模板以及由参数相关查找找到的所有重载,使得swap(t,u)swap(u,t)是有效表达式,并且交换tu的值,则对象t可以对象u进行交换。
在上述条件下交换两个对象的常规方式为:
using std::swap;
swap(t, u);

在名称查找时,会考虑 std::swap 和由 ADL 找到的任何 swap 的重载,然后重载决议将选择最佳匹配项。
考虑每个实现:
1. 不允许向命名空间 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 或 3 和 5 中的一个。通常认为最好提供一个成员交换(3),然后在同一命名空间中提供一个非成员函数(5),该函数调用成员交换。

4
正如您所说,您需要 #5(与您的类型相同命名空间中的一个函数)来支持惯用的using std :: swap; swap(a,b);。这样,当使用参数相关查找时,将选择您的重载,而不是std :: swap
如果您的swap实现需要访问该类型的私有成员,则需要调用类似于#3的成员函数,或者声明非成员函数为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)找到您的实现。


4
+1,不确定它的相关性如何,但最后两个示例并不完全相同。在最后一个示例中,swap 在命名空间级别是可访问的(定义也是声明),而通过在类内定义,它只能通过 ADL(关联名称查找)访问。特别地,如果是内联定义,则无法这样做: void (*p)(thing&,thing&) = &swap; - David Rodríguez - dribeas
@DavidRodríguez-dribeas:很有趣,我不知道这个。 - Mike Seymour
请注意,我应该稍微明确一下:仅使用friend关键字的定义时,它在命名空间级别是不可访问的。如果在第一个示例中添加了一个命名空间级别的声明:class thing {...}; void swap( thing&, thing& );,那么这两个示例将是等效的。问题在于friend声明仅在类的上下文中声明函数,并不提供命名空间级别的声明。 - David Rodríguez - dribeas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接