Phi*_*ßen 22 c++ move-semantics c++11
在他的2013年GoingNative演讲中,Scott Meyers指出,std::move
并不能保证生成的代码实际上会执行一个动作.
例:
void foo(std::string x, const std::string y) {
std::string x2 = std::move(x); // OK, will be moved
std::string y2 = std::move(y); // compiles, but will be copied
}
Run Code Online (Sandbox Code Playgroud)
这里,不能应用移动构造函数,但由于重载分辨率,将使用普通的复制构造函数.这个后备选项对于向后兼容C++ 98代码可能是至关重要的,但在上面的例子中,它很可能不是程序员想要的.
有没有办法强制调用移动构造函数?
例如,假设您要移动一个巨大的矩阵.如果您的应用程序确实依赖于要移动的Matrix,那么如果无法移动则立即获得编译错误会很棒.(否则,您可能会通过单元测试轻松地解决性能问题,并且只能在进行一些分析后才能发现.)
让我们称之为保证行动strict_move
.我希望能够编写这样的代码:
void bar(Matrix x, const Matrix y) {
Matrix x2 = strict_move(x); // OK
Matrix y2 = strict_move(y); // compile error
}
Run Code Online (Sandbox Code Playgroud)
可能吗?
编辑:
谢谢你的答案!有一些合理的要求澄清我的问题:
strict_move
输入是const,应该失败吗?strict_move
结果不会导致实际的移动操作(即使副本可能像移动一样快),也应该失败,例如,const complex<double>
?我最初的想法非常含糊:我认为Scott Meyers的例子非常令人担忧,所以我想知道是否有可能让编译器阻止这种非预期的副本.
Scott Meyers在他的演讲中提到,一般的编译器警告不是一种选择,因为它会导致大量误报.相反,我想与编译器进行通信,例如"我100%确定这必须始终导致移动操作,并且副本对于此特定类型来说太昂贵".
因此,我会毫不客气地说,strict_move
两种情况都应该失败.与此同时,我不确定什么是最好的.我没有考虑的另一个方面是noexcept
.
在我看来,确切的语义strict_move
是开放的.在编译时有助于防止一些愚蠢错误而没有严重缺点的一切都很好.
How*_*ant 19
我建议不要写一个strict_move
正在检测的将军const
.我认为这不是你真正想要的.你想要这个标志a const complex<double>
还是const pair<int, int>
?这些类型将在移动时快速复制.标记它们只会是一种刺激.
如果你想这样做,我建议改为检查是否有类型noexcept MoveConstructible
.这将完美地工作std::string
.如果string
意外调用了复制构造函数,则它不是noexcept,因此将被标记.但如果pair<int, int>
意外调用了复制构造函数,你真的关心吗?
这是一个草图,它的外观如下:
#include <utility>
#include <type_traits>
template <class T>
typename std::remove_reference<T>::type&&
noexcept_move(T&& t)
{
typedef typename std::remove_reference<T>::type Tr;
static_assert(std::is_nothrow_move_constructible<Tr>::value,
"noexcept_move requires T to be noexcept move constructible");
static_assert(std::is_nothrow_move_assignable<Tr>::value,
"noexcept_move requires T to be noexcept move assignable");
return std::move(t);
}
Run Code Online (Sandbox Code Playgroud)
我决定也要检查is_nothrow_move_assignable
,因为你不知道客户端是在构建还是分配lhs.
我选择了内部static_assert
而不是外部enable_if
因为我不期望noexcept_move
过载,并且static_assert
在触发时会产生更清晰的错误消息.
首先,我想说明你以前的答案不会完全解决你的问题.
以前的解决方案(@Kerrerk和@ 0x499602D2)失败的反例:假设您使用抛出异常的移动构造函数编写了矩阵类.现在假设您想要移动std::vector<matrix>
.这篇文章表明,如果std::vector<matrix>
实际移动了保持的矩阵类元素,你不能拥有"强异常保证" (如果第j个元素移动构造函数抛出异常会发生什么?你会丢失数据,因为没有办法恢复你已经移动的元素!).
这就是stl容器实现的原因.push_back()
,.reserve()
以及它们的移动构造函数std::move_if_noexcept
用于移动它们所持有的元素.以下是从open-std获取的reserve()的示例实现:
void reserve(size_type n)
{
if (n > this->capacity())
{
pointer new_begin = this->allocate( n );
size_type s = this->size(), i = 0;
try
{
for (;i < s; ++i)
new ((void*)(new_begin + i)) value_type( std::move_if_noexcept( (*this)[i]) ) );
}
catch(...)
{
while (i > 0) // clean up new elements
(new_begin + --i)->~value_type();
this->deallocate( new_begin ); // release storage
throw;
}
// -------- irreversible mutation starts here -----------
this->deallocate( this->begin_ );
this->begin_ = new_begin;
this->end_ = new_begin + s;
this->cap_ = new_begin + n;
}
}
Run Code Online (Sandbox Code Playgroud)
所以,如果你没有强制你的move和默认构造函数是noexcept,你不能保证像std :: vector.resize()或std :: move(对于stl容器)这样的函数永远不会复制矩阵类和这是正确的行为(否则你可能会丢失数据)
您可以只创建自己的版本,move
不允许常量返回类型.例如:
#include <utility>
#include <string>
#include <type_traits>
template <typename T>
struct enforce_nonconst
{
static_assert(!std::is_const<T>::value, "Trying an impossible move");
typedef typename std::enable_if<!std::is_const<T>::value, T>::type type;
};
template <typename T>
constexpr typename enforce_nonconst<typename std::remove_reference<T>::type>::type &&
mymove(T && t) noexcept
{
return static_cast<typename std::remove_reference<T>::type &&>(t);
}
void foo(std::string a, std::string const b)
{
std::string x = std::move(a);
std::string y = std::move(b);
}
void bar(std::string a, std::string const b)
{
std::string x = mymove(a);
// std::string y = mymove(b); // Error
}
int main() { }
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1677 次 |
最近记录: |