23

在他的 GoingNative 2013 演讲中,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
}

在这里,不能应用移动构造函数,但由于重载决议,将使用普通的复制构造函数。这个回退选项可能对于向后兼容 C++98 代码至关重要,但在上面的示例中,它很可能不是程序员想要的。

有没有办法强制调用移动构造函数?

例如,假设您要移动一个巨大的矩阵。如果您的应用程序确实依赖于要移动的矩阵,那么在无法移动的情况下立即获得编译错误会很棒。(否则,您的性能问题可能很容易通过单元测试而溜走,只有在进行一些分析后才能发现。)

让我们称之为有保证的举动strict_move。我希望能够编写这样的代码:

void bar(Matrix x, const Matrix y) {
  Matrix x2 = strict_move(x); // OK
  Matrix y2 = strict_move(y); // compile error
}

可能吗?

编辑:

感谢您的精彩回答!有一些合法的要求来澄清我的问题:

  • strict_move如果输入是 const应该失败吗?
  • 如果strict_move结果不会导致实际的移动操作(即使副本可能与移动一样快,例如const complex<double>),是否应该失败?
  • 两个都?

我最初的想法很模糊:我认为 Scott Meyers 的例子非常令人担忧,所以我想知道是否有可能让编译器阻止这种意外的复制。

Scott Meyers 在他的演讲中提到,一般编译器警告不是一种选择,因为它会导致大量误报。相反,我想与编译器进行交流,例如“我 100% 确定这必须始终导致移动操作,并且副本对于这种特定类型来说太昂贵了”。

因此,我会不经意地说这strict_move两种情况都应该失败。同时我不确定什么是最好的。我没有考虑的另一个方面是noexcept

从我的角度来看,确切的语义strict_move是开放的。任何有助于在编译时防止一些愚蠢错误而没有严重缺陷的东西都很好。

4

3 回答 3

19

我建议不要写一个strict_move正在检测的将军const。我认为这不是你真正想要的。你想让它标记 aconst 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);
}

我决定也检查一下is_nothrow_move_assignable,因为您不知道客户端是在构建还是分配 lhs。

我选择了内部static_assert而不是外部enable_if,因为我不希望noexcept_move过载,并且static_assert在触发时会产生更清晰的错误消息。

于 2013-09-05T23:19:48.700 回答
6

首先,我想声明您之前的答案并不能完全解决您的问题。

以前的解决方案(@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;
    }
}

所以,如果你不强制你的移动和默认构造函数是 noexcept,你将不能保证像 std::vector.resize() 或 std::move (对于 stl 容器)这样的函数永远不会复制矩阵类和这是正确的行为(否则您可能会丢失数据)

于 2013-09-05T23:23:53.317 回答
5

您可以制作自己的版本,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() { }
于 2013-09-05T22:31:47.850 回答