可以定义一个完全一般的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);
为x
select一个重载,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
NullablePointer
types是一个支持空值的指针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,其中x
或y
是foo::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 ++)的实现:
_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
自定义pointer
types,它应该与通常using std::swap; swap(a, b);
using std::swap; swap(a, b);
成语。 这个成语在foo::bar*
中显然被打破了。