可以定义一个完全一般的swap()函数吗?

以下片段:

#include <memory> #include <utility> namespace foo { template <typename T> void swap(T& a, T& b) { T tmp = std::move(a); a = std::move(b); b = std::move(tmp); } struct bar { }; } void baz() { std::unique_ptr<foo::bar> ptr; ptr.reset(); } 

不为我编译:

 $ g++ -std=c++11 -c foo.cpp In file included from /usr/include/c++/5.3.0/memory:81:0, from foo.cpp:1: /usr/include/c++/5.3.0/bits/unique_ptr.h: In instantiation of 'void std::unique_ptr<_Tp, _Dp>::reset(std::unique_ptr<_Tp, _Dp>::pointer) [with _Tp = foo::bar; _Dp = std::default_delete<foo::bar>; std::unique_ptr<_Tp, _Dp>::pointer = foo::bar*]': foo.cpp:20:15: required from here /usr/include/c++/5.3.0/bits/unique_ptr.h:342:6: error: call of overloaded 'swap(foo::bar*&, foo::bar*&)' is ambiguous swap(std::get<0>(_M_t), __p); ^ In file included from /usr/include/c++/5.3.0/bits/stl_pair.h:59:0, from /usr/include/c++/5.3.0/bits/stl_algobase.h:64, from /usr/include/c++/5.3.0/memory:62, from foo.cpp:1: /usr/include/c++/5.3.0/bits/move.h:176:5: note: candidate: void std::swap(_Tp&, _Tp&) [with _Tp = foo::bar*] swap(_Tp& __a, _Tp& __b) ^ foo.cpp:7:10: note: candidate: void foo::swap(T&, T&) [with T = foo::bar*] void swap(T& a, T& b) 

这是我的错误声明swap()函数如此一般,它与std::swap冲突?

如果是这样,是否有一种方法来定义foo::swap() ,使其不被Koenig查找拖拽?

  • unique_ptr<T>要求T*是一个NullablePointer [unique.ptr] p3
  • NullablePointer要求T*左值是Swappable [nullablepointer.requirements] p1
  • Swappable基本上需要using std::swap; swap(x, y); using std::swap; swap(x, y);xselect一个重载, y是typesT* [swappable.requirements] p3的左值

在最后一步,你的typesfoo::bar会产生歧义,因此违反了unique_ptr的要求。 libstdc ++的实现是一致的,虽然我会说这是相当令人吃惊的。


措辞当然更加复杂,因为它是通用的。

[unique.ptr] P3

如果remove_reference_t<D>::pointer存在,则unique_ptr<T, D>::pointer应该是remove_reference_t<D>::pointer的同义词。 否则, unique_ptr<T, D>::pointer应该是T*的同义词。 unique_ptr<T, D>::pointer应满足NullablePointer的要求。

(重点是我的)

[nullablepointer.requirements] P1

NullablePointertypes是一个支持空值的指针types。 如果符合以下条件,则P型符合NullablePointer的要求:

  • […]
  • P型左值是可交换的(17.6.3.2),
  • […]

[swappable.requirements] P2

对象t可以与对象交换当且仅当:

  • expression式swap(t, u)swap(u, t)在下面描述的上下文中评估时是有效的
  • […]

[swappable.requirements] P3

swap(t, u)swap(u, t)进行评估的上下文应确保通过候选集上的重载parsingselect名为“swap”的二进制非成员函数,该候选集包括:

  • <utility>定义的两个swap函数模板
  • 由参数相关查找产生的查找集。

请注意,对于指针typesT* ,出于ADL的目的,关联的名称空间和类是从T类派生的。 因此, foo::bar*foo作为关联的名称空间。 swap(x, y) ADL,其中xyfoo::bar*将因此findfoo::swap

问题是libstdc ++的unique_ptr的实现。 这是从他们的4.9.2分支:

https://gcc.gnu.org/onlinedocs/gcc-4.9.2/libstdc++/api/a01298_source.html#l00339

  338 void 339 reset(pointer __p = pointer()) noexcept 340 { 341 using std::swap; 342 swap(std::get<0>(_M_t), __p); 343 if (__p != pointer()) 344 get_deleter()(__p); 345 } 

正如你所看到的,有一个不合格的掉期电话。 现在让我们看看libcxx(libc ++)的实现:

https://git.io/vKzhF

 _LIBCPP_INLINE_VISIBILITY void reset(pointer __p = pointer()) _NOEXCEPT { pointer __tmp = __ptr_.first(); __ptr_.first() = __p; if (__tmp) __ptr_.second()(__tmp); } _LIBCPP_INLINE_VISIBILITY void swap(unique_ptr& __u) _NOEXCEPT {__ptr_.swap(__u.__ptr_);} 

他们不调用reset内置swap ,也不使用不合格的交换呼叫。


Dyp的回答为什么libstdc++符合要求提供了一个非常稳定的分解,而且为什么只要需要由标准库调用swap代码就会中断。 引用TemplateRex :

您应该没有理由在仅包含特定types的非常特定的命名空间中定义这样的一般swap模板。 只需为foo::bar定义一个非模板swap重载。 保留一般交换到std::swap ,只提供特定的重载。 资源

作为一个例子,这不会编译:

 std::vector<foo::bar> v; std::vector<foo::bar>().swap(v); 

如果你的平台是一个旧的标准库/ GCC(比如CentOS),我会推荐使用Boost而不是重新开发,以避免这样的陷阱。

这个技巧可以用来避免ADL发现foo::swap()

 namespace foo { namespace adl_barrier { template <typename T> void swap(T& a, T& b) { T tmp = std::move(a); a = std::move(b); b = std::move(tmp); } } using namespace adl_barrier; } 

这就是Boost.Range独立的begin() / end()函数的定义。 在提出问题之前,我尝试了类似的方法,但是using adl_barrier::swap; 相反,这是行不通的。

至于问题中的片段是否应该按原样工作,我不确定。 我可以看到的一个复杂性是, unique_ptr可以有Deleter自定义pointertypes,它应该与通常using std::swap; swap(a, b); using std::swap; swap(a, b); 成语。 这个成语在foo::bar*中显然被打破了。