C++ 如果std::move将导致意外复制,是否强制执行编译时错误?
Scott Meyers在2013年的GoingNative演讲中指出,C++ 如果std::move将导致意外复制,是否强制执行编译时错误?,c++,c++11,move-semantics,C++,C++11,Move Semantics,Scott Meyers在2013年的GoingNative演讲中指出,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 } 在这里,无法应用移动构造函数,
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
}
在这里,无法应用移动构造函数,但由于重载解析,将使用普通复制构造函数。此回退选项对于与C++98代码的向后兼容性可能至关重要,但在上面的示例中,它很可能不是程序员想要的
有没有办法强制调用移动构造函数?
例如,假设您想要移动一个巨大的矩阵。如果您的应用程序确实依赖于要移动的矩阵,那么如果无法移动,那么立即出现编译错误将是非常好的。(否则,性能问题可能很容易通过单元测试,只有在进行一些分析后才能发现。)
让我们称之为保证移动严格移动
。我希望能够编写如下代码:
void bar(Matrix x, const Matrix y) {
Matrix x2 = strict_move(x); // OK
Matrix y2 = strict_move(y); // compile error
}
可能吗
编辑:
谢谢你的回答!有一些正当的要求澄清我的问题:
- 如果输入为常量,
是否应失败strict\u move
- 如果结果不会导致实际的移动操作(即使复制速度可能与移动速度一样快,例如,
),是否应const complex
失败strict\u move
- 两者都有
strict\u move
在这两种情况下都应该失败。与此同时,我不确定什么是最好的。我没有考虑的另一个方面是<>代码>除< /代码> .<
在我看来,
strict\u move
的确切语义是开放的。任何有助于防止编译时出现一些愚蠢错误而不存在严重缺陷的方法都是可以的。您只需制作自己的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() { }
#包括
#包括
#包括
模板
结构强制执行
{
静态断言(!std::is_const::value,“尝试不可能的移动”);
typedef typename std::enable_if::value,T>::type type;
};
模板
constexpr typename强制_unconst::type&&
mymove(T&T)无例外
{
返回静态_-cast(t);
}
void foo(std::string a,std::string const b)
{
std::string x=std::move(a);
std::string y=std::move(b);
}
空栏(标准::字符串a,标准::字符串常量b)
{
std::string x=mymove(a);
//std::string y=mymove(b);//错误
}
int main(){}
我建议不要编写一个用于检测常量的常规严格的\u-move
。我想那不是你真正想要的。您想让它标记一个常量复合体
,还是一个常量对
?这些类型的复制速度与它们移动的速度一样快。给他们打旗子只会是一种刺激
如果要执行此操作,我建议改为检查类型是否为noexcept MoveConstructible
。这将非常适合std::string
。如果意外调用了string
的复制构造函数,则它不是noexcept,因此将被标记。但是如果意外调用了对的复制构造函数,您真的在乎吗
下面是一个草图,它看起来像什么:
#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);
}
#包括
#包括
模板
typename std::remove_reference::type&&
无例外移动(T&T)
{
typedef typename std::remove_reference::type Tr;
静态断言(std::is\u nothrow\u move\u constructible::value,
“noexcept_move要求T是noexcept move可构造的”);
静态断言(std::is \u nothrow\u move\u assignable::value,
“无异常移动要求T为无异常移动可分配”);
返回std::move(t);
}
我决定检查是否为非移动可分配的
,因为您不知道客户机是否正在构建或分配lhs
我选择了内部static\u assert
而不是外部enable\u if
,因为我不希望noexcept\u move
过载,当被触发时,静态断言将产生更清晰的错误信息。首先,我想声明,您以前的回答不会完全解决您的问题
以前的解决方案(@Kerrerk和@0x499602D2)失败的反例:假设您使用引发异常的移动构造函数编写矩阵类。现在假设您想要移动std::vector
。表明如果std::vector
所持有的矩阵类元素实际被移动,则无法获得“强异常保证”(如果第j个元素移动构造函数抛出异常,会发生什么情况?您将丢失数据,因为无法恢复已移动的元素!)
这就是为什么stl容器实现.push_back()
,.reserve()
和它们的move构造函数,使用std::move_if_noexcept
移动它们所持有的元素。以下是reserve()的示例实现,取自:
void reserve(大小\u类型n)
{
如果(n>此->容量())
{
指针new\u begin=this->allocate(n);
大小\类型s=this->size(),i=0;
尝试
{
对于(;ivoid 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;
}
}