如何为我的班级提供交换功能?


87

启用我swap的STL算法的正确方法是什么?

1)会员swap。是否std::swap使用SFINAE技巧来使用成员swap

2)swap在同一个命名空间中独立。

3)的部分专业化std::swap

4)以上全部。

谢谢。

编辑:好像我没有清楚地说出我的问题。基本上,我有一个模板类,我需要STL算法才能使用为该类编写的(有效)交换方法。

Answers:


94
  1. 是正确使用swap。当您编写“库”代码并要在上启用ADL(依赖于参数的查找)时,请以这种方式编写swap。另外,这与SFINAE无关。
// some algorithm in your code
template<class T>
void foo(T& lhs, T& rhs) {
    using std::swap; // enable 'std::swap' to be found
                    // if no other 'swap' is found through ADL
    // some code ...
    swap(lhs, rhs); // unqualified call, uses ADL and finds a fitting 'swap'
                    // or falls back on 'std::swap'
    // more code ...
}
  1. swap为您的类提供功能的正确方法。
namespace Foo {

class Bar{}; // dummy

void swap(Bar& lhs, Bar& rhs) {
    // ...
}

}

如果swap现在按1)所示使用,将找到您的功能。另外,如果您确实需要,可以将该函数设为朋友,或者提供一个swap由free函数调用的成员:

// version 1
class Bar{
public:
    friend void swap(Bar& lhs, Bar& rhs) {
    // ....
    }
};

// version 2
class Bar{
public:
    void swap(Bar& other) {
    // ...
    }
};

void swap(Bar& lhs, Bar& rhs) {
    lhs.swap(rhs);
}

...
  1. 您的意思是明确的专业化。Partial仍然是其他东西,对于函数,仅结构/类,也是不可能的。因此,由于您不能专门std::swap研究模板类,因此必须在名称空间中提供免费功能。如果我可以这么说,这不是一件坏事。现在,也可以进行显式专门化,但是通常您不想专门对功能模板进行专门化
namespace std
{  // only allowed to extend namespace std with specializations

template<> // specialization
void swap<Bar>(Bar& lhs, Bar& rhs) noexcept {
    // ...
}

}
  1. 不,因为1)与2)和3)不同。同样,同时拥有2)和3)会导致总是选择2),因为它更合适。

8
除非我误读了内容,否则您的(1)和问题的(1)不会真正对齐。不过,+ 1
丹尼斯·齐克福斯

1
@Xeo。感谢您的输入。我编辑了问题。STL是否按照情况1所述使用交换?
pic11 2011年

1
@pic:是的,STL将使用我在1)中显示的ADL交换,但是仅当它作为自由函数存在时,才使用成员函数。参见2)和3),两个版本都将由算法选择。我建议2),因为3)已过时,被认为是不良做法。
Xeo

2
第一段代码中的注释具有误导性。using std::swap;没有启用ADL,它只是允许编译器定位std::swapADL是否找不到适当的重载。
DavidRodríguez-dribeas 2011年

6
这个答案在技术上是正确的,但是非常需要进行编辑以求清楚。OP的(1)不是正确的答案,因为该答案中的过快读数似乎会错误地表明。
Howard Hinnant

1

要回答EDIT(类可能是模板类),则根本不需要专门化。考虑这样的一个类:

template <class T>
struct vec3
{
    T x,y,z;
};

您可以定义以下类:

vec3<float> a;
vec3<double> b;
vec3<int> c;

如果您希望能够创建一个函数来实现所有3个交换(并非本示例类保证了此交换),您可以像Xeo在(2)中所说的那样做...而无需专门化,而只需创建一个常规模板函数即可:

template <class T>
void swap(vec3<T> &a, vec3<T> &b)
{
    using std::swap;
    swap(a.x,b.x);
    swap(a.y,b.y);
    swap(a.z,b.z);
}

交换模板函数应与您要交换的类位于相同的名称空间中。即使您未使用ADL引用该名称空间,以下方法也将找到并使用该交换:

using std::swap;
swap(a,b);

0

似乎(2)(在声明用户定义的类的同一名称空间中自由站立swap)是提供swap用户定义的类的唯一允许方式,因为向声明名称空间添加声明std通常是未定义的行为。扩展命名空间std(cppreference.com)

将声明或定义添加到名称空间std或嵌套在std其中的任何名称空间是未定义的行为,以下有一些例外

并且swap不表示为这些例外之一。因此,将您自己的swap重载添加到std名称空间是未定义的行为。

也有人说,如果提供了用户swap定义的标准库,则标准库使用对函数的非限定调用来swap为用户类调用用户定义的swap

可交换(cppreference.com)

许多标准库函数(例如,许多算法)都希望其参数满足Swappable的要求,这意味着,每当标准库执行交换时,它都使用的等效项using std::swap; swap(t, u);

调换(www.cplusplus.com)

标准库中的许多组件(以内stdswap无限定的方式调用,以允许调用非基本类型的自定义重载,而不是此通用版本:swap选择与提供它们的类型相同的名称空间中声明的自定义重载通过对该通用版本进行依赖参数的查找

但是请注意,直接将std::swap函数用于用户定义的类会调用的通用版本,std::swap而不是用户定义的类swap

my::object a, b;
std::swap(a, b); // calls std::swap, not my::swap

因此,建议以swap与在标准库中相同的方式在用户代码中调用该函数:

my::object a, b;
using std::swap;
swap(a, b); // calls my::swap if it is defined, or std::swap if it is not.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.