【发布时间】:2018-11-05 21:20:14
【问题描述】:
我建议swap的这个实现,如果有效,优于std::swap的当前实现:
#include <new>
#include <type_traits>
template<typename T>
auto swap(T &t1, T &t2) ->
typename std::enable_if<
std::is_nothrow_move_constructible<T>::value
>::type
{
alignas(T) char space[sizeof(T)];
auto asT = new(space) T{std::move(t1)};
// a bunch of chars are allowed to alias T
new(&t1) T{std::move(t2)};
new(&t2) T{std::move(*asT)};
}
page for std::swap at cppreference 暗示它使用移动赋值,因为 noexcept 规范取决于移动赋值是否为无抛出。此外,how is swap implemented 已在此处询问,这就是我在 libstdc++ 和 libc++ 的实现中看到的内容
template<typename T>
void typicalImplementation(T &t1, T &t2)
noexcept(
std::is_nothrow_move_constructible<T>::value &&
std::is_nothrow_move_assignable<T>::value
)
{
T tmp{std::move(t1)};
t1 = std::move(t2);
// this move-assignment may do work on t2 which is
// unnecessary since t2 will be reused immediately
t2 = std::move(tmp);
// this move-assignment may do work on tmp which is
// unnecessary since tmp will be immediately destroyed
// implicitly tmp is destroyed
}
我不喜欢在t1 = std::move(t2) 中使用移动分配,因为这意味着如果资源被持有,则执行代码以释放t1 中持有的资源,即使已知t1 中的资源已经释放。我有一个实际案例,其中通过虚拟方法调用释放资源,因此编译器无法消除不必要的工作,因为它不知道虚拟覆盖代码,无论它是什么,都不会做任何事情,因为没有在t1 中发布的资源。
如果这在实践中是非法的,请指出它违反了标准吗?
到目前为止,我已经在答案中看到了两个可能使这非法的反对意见:
-
tmp中创建的临时对象不会被破坏,但用户代码中可能存在一些假设,即如果构造了T,它将被破坏 -
T可能是具有无法更改的常量或引用的类型,移动分配可以通过交换资源来实现,而无需触及这些常量或重新绑定引用。
因此,这种构造似乎对任何类型都是合法的,除了那些遇到上述情况 1 或 2 的类型。
为了说明,我放了一个指向compiler explorer 页面的链接,该页面显示了交换整数向量情况的所有三种实现,即std::swap 的典型默认实现,vector 的专用实现,以及我提议的那个。您可能会看到建议的实现执行的工作量比典型的少,与标准中的专用实现完全相同。
只有用户可以决定交换“全移动构造”与“一个移动构造,两个移动分配”,您的答案会告知用户“全移动构造”无效。
在与同事进行了更多的边带对话之后,我的要求归结为这适用于移动可以被视为破坏性的类型,因此无需平衡构造与破坏。
【问题讨论】:
-
移动分配比移动构造更快。而且我很确定如果您将 new 放入有效对象的内存中,那么该有效对象永远不会被破坏。
-
根据 Rakete 下面提到的标准部分,在依赖于其析构函数的任何类型上使用此表单是 UB。
-
std::swap 也不会让用户认为构造函数会被调用,所以这种行为很容易引起误解。假设您的类型中有一个对象实例计数器..每次调用 swap 它都会增加 3?这对“交换”没有任何意义
-
@xaxxon 这是 libstdc++ 如何使用移动构造函数实现交换的链接:github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/…
-
行
auto lt1 = std::launder(&t1);使得lt1可以访问新对象,但是对t1没有影响,所以还是UB使用t1访问新对象。
标签: c++ language-lawyer standards swap c++-standard-library