为什么 std::swap 不使用 swap 习语?
Why is std::swap not using swap idiom?
因为正确使用std::swap是:
using std::swap;
swap(a,b);
这有点冗长,但它确保如果 a,b 定义了更好的交换,它就会被选中。
所以现在我的问题是为什么不使用这种技术实现std::swap
所以用户代码只需要调用std::swap
?
所以像这样的事情(为了简洁起见,忽略noexcept
和约束):
namespace std {
namespace internal {
template <class T> // normal swap implementation
void swap(T& a, T& b) { // not intended to be called directly
T tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}
}
template <class T>
void swap(T& a, T& b) {
using internal::swap;
swap(a,b);
}
}
这是进入重言式领域,但它不是那样做的,因为这不是它的目的。
std::swap
的目的是成为最后手段的交换功能。它不应该是你直接调用的东西,除非你真正想要的是使用最后手段的交换。
现在,您可以争辩说,您建议的是自定义点的更好范例。事后诸葛亮总是 20/20;并非STL所做的一切都是正确的想法(见vector<bool>
)。
至于为什么我们现在不能改变这一点,那是另一回事了。std::swap
是最后手段的交换功能。因此,从理论上讲,人们可能会调用它并期望它绕过任何用户定义的交换代码。因此,以这种方式更改它会破坏他们的代码。
这是这种方法的一个问题。ADL"两步"依赖于 ADL 发现比正常匹配更好的函数(否则,会出现过载解析失败)。这在大多数情况下都很好,因为当您在用户定义的命名空间中为用户定义类型编写swap()
时,您正在编写特定于这些类型的函数。
但是对于标准库类型,可能没有比简单算法更有效swap()
,这被分解了。例:
namespace N {
namespace internal {
template <typename T>
void swap(T&, T&); // normal swap impl
}
template <typename T>
void swap(T& a, T& b) {
using internal::swap;
swap(a, b); // (*)
}
struct C { };
}
N::C c;
swap(c, c); // error
N::swap(c, c); // error
出于同样的原因,这两个调用都失败了。对标记为(*)
swap()
的非限定调用将通过正常的非限定查找查找N::internal::swap()
,然后通过 ADL 查找N::swap()
。没有办法区分这些调用,因为你非常需要这两个调用都适用于满足swap
约束的所有类型。生成的调用不明确。
所以这样的设计需要为namespace std
中的每个类型添加一个新的swap()
函数,即使它只是转发到std::internal::swap()
。
从历史上看,似乎对名称解析没有太多考虑。std::swap
被设计为一个自定义点,但也最终被设计为在通用代码中调用以交换事物的函数。因此,如果std::swap
不起作用,或者太慢,那么即使 ADL 已经有一个非常好的swap
,也可能不得不重载它。如果不破坏或更改现有代码的含义,则无法更改此设计。现在有些情况下,为了性能起见,委员会很高兴地决定改变现有代码的含义,例如隐式移动语义(例如,按值传递临时代码时,或未实现省略的 RVO)。所以这并不完全一致。尽管如此,将std::swap
从自定义点更改为具有所有现有std::swap
重载的名称解析包装器是可疑的。这绝对可能导致在编写不佳的遗留代码中触发灾难性错误。
另一个重要原因是,IMO,你不能在保持其通用性的同时将此习惯用法移动到std
命名空间。 例如:
namespace A { struct X{}; }
namespace B {
using std::swap;
void swap(A::X&, A::X&);
template<typename T>
void reverse(T (&ts)[4])
{
swap(ts[0], ts[3]);
swap(ts[1], ts[2]);
}
void silly(A::X (&xs)[4])
{
reverse(xs);
}
}
在这里,silly
最终使用B::swap
.这样做的原因是std::swap
(通过using
)和B::swap
都以相同的优先级可见,但后者是更好的匹配。现在,你可能会争辩说这是一个愚蠢的例子,所以这里有另一个不那么做作的例子:
namespace types { /*...*/ }
namespace algorithms { /*including some swap implementations for types from above...*/ }
template<typename T>
void reverse(T (&ts)[4])
{
using std::swap;
using algorithms::swap;
swap(ts[0], ts[3]);
swap(ts[1], ts[2]);
}
这将使用algorithms
的交换函数,如果它比任何std::swap
重载都匹配得更好,但 ADL 不会找到algorithms::swap
,因此查找通常不能在std::swap
内部进行。此处可能涉及任意数量的命名空间。
- 概念可以与 CRTP 习语一起使用吗?
- 复制和交换习语和迭代器
- 为什么当我做复制和交换习语时不调用我的复制构造函数?
- 漂亮的计数器习语的错误或格式错误的静态订单惨败?
- C++17 pimpl 习语上下文中的自定义迭代器
- 在 MyVector 中实现写入时复制习语
- 视觉 "extern __forceinline "是什么 c++ 习语?
- C 习语使用C库时,该C库期望功能指针
- 为什么 std::swap 不使用 swap 习语?
- 如何使用擦除删除习语从结构向量中删除值
- C++中的Singleton习语
- 在 C++11 中实现复制和交换习语的更好方法
- N 个并发读取器和 1 个生产者的习语或模式
- pImpl习语-将私有类实现放在cpp中有什么缺点
- 不能使用 pimpl 习语将用户定义的向量插入到封装的向量中
- Python -> C++ 习语:将 lambda 表达式存储在映射/容器中
- 安全方便的通用散列(用于 STL 无序集和映射)习语?
- 指向可配置实现的Pimpl习语
- 与构造函数参数相关的异常安全的习语
- 用C++处理身体习语