我建议 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
规范取决于移动赋值是否为 no-throw。此外,这里已经有人问过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
可能是具有无法更改的常量或引用的类型,移动分配可以通过交换资源来实现,而无需触及这些常量或重新绑定(bind)引用。
因此,似乎此构造对于除上述情况 1 或 2 之外的任何类型都是合法的。
为了便于说明,我放置了一个指向 compiler explorer 的链接。页面显示了交换整数 vector 情况的所有三种实现,即 std::swap
的典型默认实现、专门用于 vector
的实现以及一个我提议。您可能会发现建议的实现比典型的实现执行的工作更少,与标准中的专门实现完全相同。
只有用户可以决定交换“全移动构建”与“一步构建,两次移动分配”,您的答案会告知用户“全移动构建”无效。
在与同事进行更多边带对话之后,我所要求的归结为这适用于移动被视为破坏性的类型,因此无需平衡构造与破坏。
最佳答案
如果 T
具有 ref 或 const 成员,则这是非法的。使用 std::launder
或存储新的指针(请参阅 [basic.life]p8 )
auto asT = new(space) T{std::move(t1)};
// or use std::launder
但是您还需要对 t1
和 t2
使用 std::launder
!这里有一个问题,因为如果没有 std::launder
,t1
和 t2
会引用旧的(已经被破坏的)值并且不会' t 指的是新构造的对象。对 t1
和 t2
的任何访问都是 UB。
I dislike using move-assignment in
swap
because it implies the destruction of already-moved objects, which seems unnecessary work, hence this implementation.
过早优化?现在您需要调用两个析构函数(t1
和 t2
)!而且,析构函数调用实际上并不昂贵。
现在,正如 Nathan Oliver 所说,析构函数不会被调用(这不是 UB),但你确实不应该这样做,因为析构函数可能会做重要的事情。
关于c++ - 根据标准,这种没有破坏的交换实现是否有效?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53162396/