21

我今天读到一些有趣的东西,说在用户提供的类型(作为模板参数提供)上调用交换的“标准”方式是......

using std::swap;
swap(something, soemthingelse);

这样做的原因是使用参数相关查找来使用swap用户命名空间或命名空间swap中的函数std。这对我提出了一个感兴趣的问题。当我为我的一个类重载时std::swap,我实际上是在命名空间中定义它std...。namespace std { void swap(/*...*/){/*...*/} }这种做法有吗?我应该定义我自己swap的 sstd还是我自己的命名空间(为什么)?

4

5 回答 5

21

你这样做是错的 :)

17.6.2.4.1 [命名空间.std]

  1. std如果 C++ 程序将声明或定义添加到命名空间或命名空间内的命名空间,则 C++ 程序的行为是未定义的,std除非另有说明。std只有当声明依赖于用户定义的类型并且特化满足原始模板的标准库要求并且没有明确禁止时,程序才能将任何标准库模板的模板特化添加到命名空间。

这很清楚地表明您可能不会向 namespace 添加重载std。您可以专门std::swap<MyType>针对您的类型,但如果您的类型是模板,则需要部分专门化,std::swap<MyContainer<T>>并且您不能部分专门化函数模板,因此这不起作用,因此一般来说这不是一个好方法。

C++11 还定义了可交换类型的要求,包括:

17.6.3.2 [swappable.requirements]

  1. ...
  2. ...
  3. swap(t, u)评估和的上下文swap(u, t)应确保通过重载决议(13.3)在候选集上选择名为“swap”的二进制非成员函数,该候选集包括:
  • <utility>(20.2)中定义的两个交换函数模板和
  • 由参数相关查找(3.4.2)产生的查找集。

因此调用swap两个可交换类型的对象应该能够找到std::swap并且应该能够通过 ADL 找到其他重载。将其称为不合格(并且没有显式模板参数列表)可确保 ADL 发生,并包含<utility>并添加 using-declaration forstd::swap确保可以找到标准重载。因此,按照您在问题中显示的方式进行操作符合这些要求。

这很清楚地定义了在标准使用的意义上可交换需要什么,这是标准库所要求的,例如<algorithm>.

如果您将swap类型的重载放在类型的命名空间中,那么 ADL 可以找到它们。无论如何,这是正确的做法,与您的类型相关的函数与您的类型属于同一命名空间,有关该主题的更多详细信息,请参阅 Sutter 和 Alexandrescu 的C++ 编码标准中的第 57 条。

所以简而言之,你做错了。你读的是正确的。执行using std::swap和依赖 ADL 始终有效(对于模板和非模板)并避免未定义的行为。耶。

注意 C++03 标准对如何交换用户定义的类型不太清楚。有关该领域的一些历史,请参阅N1691 2.2,它定义了术语自定义点并显示了在 API 中定义它们的不同方法。C++11 中用于交换类型的协议使用了其中一种方式,现在被明确无误地祝福为为您的类型提供交换功能的“正确方式”。其他库中的其他自定义点可以使用其他方法,但在 C++11 术语中可交换意味着using std::swap;并依赖于 ADL。

于 2013-01-18T16:57:09.213 回答
6

为您自己的类型提供标准模板的特化是合法的,并且这些模板必须放在std命名空间内。所以这两种方法都是合法的 C++。

话虽如此,推荐的方法是swap在与您自己的类型相同的命名空间中提供免费函数,并让 ADL 从那里拾取您的重载。


编辑:在一些评论之后,我重新阅读了这个问题,并注意到它提到了命名空间中的重载std。在命名空间中提供重载是非法std的,只允许特化。

于 2013-01-18T16:30:42.313 回答
4

我相信这就是您正在寻找的答案,并且该问题的整个答案集都可以解释一切。我认为 Howard Hinnant 和 Dave Abrahams 已经在 C++ 标准委员会工作了十多年。

于 2013-01-18T17:57:39.103 回答
3

首先,你不允许添加东西std(只是一个重载),所以重载std::swap首先是不好的做法。您可以做的是专门化现有模板。所以你应该专注于而不是重载:

namespace std
{
    template<> void swap<MyType>(...) {...}
}

但是问题仍然存在,如果一个自己的命名空间版本比std-specialization 更受欢迎。swapidomatic 推荐的方法是在您自己的命名空间中提供一个免费的,并让 ADL 解决它,就像David已经建议的那样。这样做的问题是,如果某人(您的库的客户)不是那么精通惯用的 C++ 并且只是显式地到处调用std::swap(...),那么您最终可能会遇到次优的交换行为。这就是为什么我自己通常会采取安全的方式来做这两者(自己的命名空间函数 + std-specialization),即使这对我来说总是很糟糕。

但幸运的是,C++11 简化了事情,因为有效的可移动类型(通常也是有效可交换的类型)可以与std::swap使用移动语义的 default 非常有效地交换。std::swap因此,在您自己的函数上可能使用默认值swap变得不那么不利(您甚至可能考虑不费心提供一个)。

因此,如果您拥有 C++11,最好的方法确实是不要打扰扩展,std而只需在类型的名称空间中定义自己swap的名称空间,而在 C++03 中,您可能希望通过std::swap专业化来确保安全,即使不那么惯用语。

于 2013-01-18T16:36:01.293 回答
0

理想的做法是仅在您认为 std::swap 对您的类型效率低下时才提供公共交换成员函数。如果您提供交换成员函数,建议您还提供调用该成员函数的非成员交换。这使得其他人更容易调用您更有效的模板特定版本。

于 2013-01-25T01:22:54.127 回答