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

时间:2013-01-18 16:14:42

标签: c++ stl

我今天读到了一些有趣的内容,表示在用户提供的类型上调用swap的“标准”方式(作为模板参数提供)是......

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

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

5 个答案:

答案 0 :(得分:20)

你做错了:)

  

17.6.2.4.1 [namespace.std]

     
    
        
  1. 如果C ++程序向名称空间std或名称空间std中的名称空间添加声明或定义,则它是未定义的,除非另有说明。只有当声明取决于用户定义的类型且专业化符合原始模板的标准库要求且未明确禁止时,程序才可以将任何标准库模板的模板特化添加到命名空间std
  2.        

很明显,您可能不会向命名空间std添加重载。您可以为您的类型专门设置std::swap<MyType>,但如果您的类型是模板,则需要部分特化,std::swap<MyContainer<T>>并且您不能部分专门化功能模板,所以这不会起作用,所以一般来说这不是一个好方法。

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

  

17.6.3.2 [swappable.requirements]

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

因此,在两个可交换类型的对象上调用swap应该能够找到std::swap并且应该能够通过ADL找到其他重载。调用它不合格(并且没有显式模板参数列表)可确保ADL发生,包括<utility>并添加std::swap的using声明可确保找到标准重载。因此,按照您在问题中显示的方式来满足这些要求。

这清楚地定义了标准使用的可交换所需的内容,这是标准库所要求的,例如:通过<algorithm>中的函数。

如果您在类型的命名空间中为您的类型添加了swap重载,那么ADL就可以找到它们。无论如何,这是正确的做法,与您的类型相关的函数属于与您的类型相同的命名空间,请参阅Sutter和Alexandrescu在C++ Coding Standards中的第57项,了解有关该主题的更多详细信息。

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

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

答案 1 :(得分:6)

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

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


编辑:在一些评论之后,我重新阅读了该问题并发现它在std命名空间中提到了重载非法std命名空间中提供重载,只允许特化

答案 2 :(得分:4)

我相信this is the answer you're looking for,这个问题的整套答案都解释了所有问题。我想,Howard Hinnant和Dave Abrahams已经在C ++标准委员会工作了十多年。

答案 3 :(得分:3)

首先,您不能向std添加内容(只是一个重载),因此首先重载std::swap是不好的做法。您可以做的是专门化现有模板。所以你应该专注而不是超载:

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

但问题仍然是,自我命名空间版本是否优于std - 专业化。推荐的idomatic方法是在您自己的命名空间中提供免费的swap,让ADL解析它,就像 David 已经建议的那样。这个问题是,如果有人(你的库的客户端)不熟悉惯用的C ++并且只是在所有地方显式调用std::swap(...),你可能最终会得到次优的交换行为。这就是为什么我自己通常采取安全的方式同时做两件事(自己的命名空间函数+ std - 专业化),即使这对我总是有不好的品味。

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

所以如果你有C ++ 11,那么最好的方法就是不要费心扩展std而只是在你的类型命名空间中定义你自己的swap,而在C ++ 03你可能会希望保持安全,std::swap专业化,即使不那么惯用。

答案 4 :(得分:0)

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