c++ - 根据标准,这种没有破坏的交换实现是否有效?
问题描述
如果有效,我建议此实现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)};
}
at cppreference的页面std::swap
暗示它使用移动分配,因为noexcept
规范取决于移动分配是否为无抛出。此外,这里有人问过它是如何swap
实现的,这就是我在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
不会被破坏,但用户代码中可能存在一些假设,即如果 aT
被构造,它将被破坏 T
可能是具有无法更改的常量或引用的类型,移动分配可以通过交换资源而不触及这些常量或重新绑定引用来实现。
因此,似乎这个构造对于任何类型都是合法的,除了上面的情况 1 或 2。
为了说明,我放置了一个指向编译器资源管理器页面的链接,该页面显示了交换整数向量的所有三种实现,即 的典型默认实现std::swap
、专用的 forvector
和我提议的那个。您可能会看到建议的实现执行的工作比典型的要少,与标准中的专用实现完全相同。
只有用户可以决定交换执行“全部移动构造”与“一次移动构造,两次移动分配”,您的答案会告知用户“全部移动构造”无效。
在与同事进行了更多的边带对话之后,我的要求归结为这适用于移动可以被视为破坏性的类型,因此无需平衡构造与破坏。
解决方案
请注意,移动构造函数和赋值运算符需要将其参数保持在有效状态。通常,实现将默认构造状态,然后与参数的状态交换以窃取其资源。根据对象想要维护的不变量,这可能仍然让参数拥有它依赖析构函数回收的资源。如果忽略破坏,这些将泄漏。
例如考虑:
class X
{
public:
X(): resource_( std::move( allocate_resource() ) )
X( X&& other ): X()
{
std::swap( resource_, other.resource_ );
}
private:
std::shared_ptr<Y> resource_;
};
然后,
X a;
X b;
swap( a, b );
现在,如果按照您的建议实施交换,那么在您做的时候
new(&t2) T{std::move(*asT)};
我们泄漏了资源的一个实例,因为移动构造函数在 *asT 分配了一个来替换它窃取的那个,并且它永远不会被破坏。
另一种看待这个问题的方式是,要么销毁什么都不做,因此便宜/免费,因此不能证明神秘肉优化的合理性,或者销毁的成本足以关心,在这种情况下,它正在做某事,所以落后了对象的背部避免做那件事在道德上是错误的,并且会导致坏事;而是修复对象实现。
推荐阅读
- xcode - clang:错误:链接器命令失败,退出代码为 1(使用 -v 查看调用) Flutter 运行构建失败
- mysql - 如何更新 MySQL(AuroraDB) 中的多行?
- c# - 将未知数量的子节点加载到 TreeNode
- android - 无法在 recyclerview 中使用 glide 加载图像(recyclerview 的数据是使用改造来获取的)
- julia - 在 Julia 中将数据集拆分为训练和测试
- r - 在 ggplot2 中用其他类型的图绘制多个 wordcloud(ggwordcloud)
- parsing - 解析 prometheus 指标数据以添加标签并重新解析为 prometheus 指标格式
- json - 对服务器的 Http.post 请求不会随它一起发送正文。在服务器端,请求的主体是空的。我在下面添加了代码
- c - 在整个程序执行过程中保持文件打开,而不是在需要时打开并关闭
- vue.js - (npm 错误)创建 Vue-CLI 项目时