C++ 如果std::move将导致意外复制,是否强制执行编译时错误?

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 } 在这里,无法应用移动构造函数,

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
}
在这里,无法应用移动构造函数,但由于重载解析,将使用普通复制构造函数。此回退选项对于与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
    失败
  • 两者都有
我最初的想法非常模糊:我认为Scott Meyers的示例非常令人担忧,因此我想知道是否有可能让编译器防止此类意外复制

Scott Meyers在他的演讲中提到,通用编译器警告不是一个选项,因为它会导致大量误报。相反,我希望与编译器通信,比如“我100%确定这一定会导致移动操作,并且对于这种特定类型,副本的成本太高”

因此,我会毫不客气地说,
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;
    }
}