你应该在 std 命名空间中重载交换吗?

2022-01-07 00:00:00 c++ stl

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

使用 std::swap;交换(东西,其他东西);

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

解决方案

你做错了 :)

<块引用>

17.6.2.4.1 [namespace.std]

<块引用>

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

这很清楚地表明您不能向命名空间 std 添加重载.你可以为你的类型专门化std::swap,但如果你的类型是一个模板,你需要一个部分专门化,std::swap<MyContainer<T>> 并且您不能部分专门化一个函数模板,所以这将不起作用,所以它通常不是一个好方法.

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

<块引用>

17.6.3.2 [swappable.requirements]

<块引用>

  1. ...
  2. ...
  3. swap(t, u)swap(u, t) 求值的上下文应确保名为swap"的二进制非成员函数是通过重载决议 (13.3) 在包含以下内容的候选集上选择:

<块引用>

  • (20.2) 和
  • 中定义的两个交换函数模板
  • 由参数相关查找 (3.4.2) 生成的查找集.

所以在两个可交换类型的对象上调用 swap 应该能够找到 std::swap 并且应该能够通过 ADL 找到其他重载.将其称为非限定(并且没有明确的模板参数列表)可确保 ADL 发生,并且包括 并为 std::swap 添加 using 声明确保标准可以找到重载.因此,您在问题中的表现方式符合这些要求.

这很清楚地定义了在标准使用的意义上可交换需要什么,这是标准库所要求的,例如通过中的函数.

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

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

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

I read something interesting today that said the 'standard' way to call swap on a user provided type (provided as a template argument) is...

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

The reason for this is to use argument dependent look-up to either use a swap function in a user namespace or swap in the std namespace. This raised an interested question for me. When I overload std::swap for one of my classes I had actually been defining it in the std namespace... namespace std { void swap(/*...*/){/*...*/} }. Is this practice wrong? Should I define my own swaps in std or my own namespace (and why)?

解决方案

You're doing it wrong :)

17.6.2.4.1 [namespace.std]

  1. The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std unless otherwise specified. A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.

That pretty clearly says you may not add overloads to namespace std. You could specialize std::swap<MyType> for your type, but if your type is a template you'd need a partial specialization, std::swap<MyContainer<T>> and you can't partially specialize a function template, so that won't work, so it's not a good approach in general.

C++11 also defines the requirements for a type to be swappable which include:

17.6.3.2 [swappable.requirements]

  1. ...
  2. ...
  3. The context in which swap(t, u) and swap(u, t) are evaluated shall ensure that a binary non-member function named "swap" is selected via overload resolution (13.3) on a candidate set that includes:

  • the two swap function templates defined in <utility> (20.2) and
  • the lookup set produced by argument-dependent lookup (3.4.2).

So calling swap on two objects of swappable type should be able to find std::swap and should be able to find other overloads by ADL. Calling it unqualified (and without an explicit template argument list) ensures ADL happens, and including <utility> and adding the using-declaration for std::swap ensures the standard overloads can be found. So doing it the way you show in your question meets those requirements.

That pretty clearly defines what it takes to be swappable in the sense used by the standard, which is what is required by the standard library e.g. by the functions in <algorithm>.

If you put swap overloads for your type in your type's namespace then they can be found by ADL. That is the right thing to do anyway, functions related to your type belong in the same namespace as your type, see Item 57 in C++ Coding Standards by Sutter and Alexandrescu for more details on that topic.

So in short, you have been doing it wrong. What you read is correct. Doing using std::swap and relying on ADL always works (for templates and non-templates) and avoids undefined behaviour. Yay.

N.B. The C++03 standard was less clear about how user-defined types should be swapped. For some history around this area see N1691 2.2 which defines the term customization point and shows different ways to define them in APIs. The protocol used in C++11 for swapping types uses one of those ways, and is now clearly and unambiguously blessed as the "correct way" to provide a swap function for your type. Other customization points in other libraries can use other approaches, but to be swappable in C++11 terms means using std::swap; and relying on ADL.

相关文章