这是C++'move'语义的正确用法吗?

Moo*_*ice 33 c++ c++-standard-library move-semantics c++11

今晚我一直在看一些我过去几天一直在研究的代码,并开始阅读移动语义,特别是std :: move.我有几个问题要求专业人士确保我走正确的道路而不做任何愚蠢的假设!

首先:

1)最初,我的代码有一个返回大向量的函数:

template<class T> class MyObject
{
public:
    std::vector<T> doSomething() const;
    {
        std::vector<T> theVector;

        // produce/work with a vector right here

        return(theVector);
    }; // eo doSomething
};  // eo class MyObject
Run Code Online (Sandbox Code Playgroud)

鉴于"theVector"在这个和"扔掉"中是暂时的,我将该函数修改为:

    std::vector<T>&& doSomething() const;
    {
        std::vector<T> theVector;

        // produce/work with a vector right here

        return(static_cast<std::vector<T>&&>(theVector));
    }; // eo doSomething
Run Code Online (Sandbox Code Playgroud)

它是否正确?这样做有什么陷阱吗?

2)我在一个函数中注意到它返回std::string它自动调用移动构造函数.调试返回字符串(thankyou,Aragorn),我注意到它称为显式移动构造函数.为什么有一个字符串类而不是矢量?

我没有必要对此函数进行任何修改以利用移动语义:

// below, no need for std::string&& return value?
std::string AnyConverter::toString(const boost::any& _val) const
{
    string ret;
    // convert here
    return(ret); // No need for static_cast<std::string&&> ?
}; // eo toString
Run Code Online (Sandbox Code Playgroud)

3)最后,我想做一些性能测试,是因为std :: move语义得到了惊人的快速结果,还是我的编译器(VS2010)做了一些优化?

(_getMilliseconds()为简洁省略的实施)

std::vector<int> v;
for(int a(0); a < 1000000; ++a)
    v.push_back(a);

std::vector<int> x;
for(int a(0); a < 1000000; ++a)
    x.push_back(a);

    int s1 = _getMilliseconds();
std::vector<int> v2 = v;
    int s2 =  _getMilliseconds();
std::vector<int> v3 = std::move(x);
    int s3 =  _getMilliseconds();

    int result1 = s2 - s1;
    int result2 = s3 - s2;
Run Code Online (Sandbox Code Playgroud)

结果显然非常棒.result1,标准作业,耗时630ms.第二个结果是0ms.这是对这些事情的良好性能测试吗?

我知道其中一些对你们很明显很明显,但我想确保在我对代码进行开拓之前理解语义.

提前致谢!

GMa*_*ckG 36

参考仍然是参考.以同样的方式,你不能在C++ 03中返回对本地的引用(或者你得到UB),你不能在C++ 0x中.你最终会得到一个死对象的引用; 它恰好是一个右值参考.所以这是错的:

std::vector<T>&& doSomething() const
{
    std::vector<T> local;

    return local; // oops
    return std::move(local); // also oops
}
Run Code Online (Sandbox Code Playgroud)

你应该做你在第二个看到的:

// okay, return by-value 
std::vector<T> doSomething() const
{
    std::vector<T> local;

    return local; // exactly the same as:
    return std::move(local); // move-construct value
}
Run Code Online (Sandbox Code Playgroud)

返回时函数的局部变量是临时的,因此无需更改任何代码.返回类型是负责实现移动语义的东西,而不是你.

你想用它std::move明确地移动一些东西,当它不能正常地完成时,就像你的测试一样.(这似乎很好;是Release中的那个吗?你应该输出向量的内容,否则编译器会优化它.)

如果您想了解右值参考,请阅读此内容.

  • @Moo:然后测试没用了.您必须在Release中进行分析,最好在真实应用程序中进行分析.在这种情况下确实没有必要; 移动语义只是赢了. (4认同)
  • 我似乎记得`return std :: move(local)`可以防止copy-elision,所以我不会认为它与`return local`相同,但我不是100%肯定. (4认同)
  • 您的链接显示"An*lvalue*是一个表达式`e`,其属性为`e = [...];`这样的行将不会导致除...之外的编译错误",这是错误的.`std :: string("hello")="world"`编译完全正常,虽然`std :: string("hello")`是一个右值(确切地说是一个*prvalue*).它还声称"An*rvalue*是一个引用内存位置但不是左值的表达式",尽管`42`*不是指内存位置.坦率地说,我不建议从该链接学习移动语义... (2认同)

fre*_*low 14

return(theVector);
Run Code Online (Sandbox Code Playgroud)

由于特殊的语言规则,这已经隐式移动,因为它theVector是一个本地对象.见第12.8节第34和35段:

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使该对象的复制/移动构造函数和/或析构函数具有副作用.在这种情况下,实现将省略的复制/移动操作的源和目标视为仅仅两种不同的引用同一对象的方式,并且该对象的销毁发生在两个对象的后期时间.没有优化就被破坏了.复制/移动操作的省略,称为复制省略,在以下情况下允许(可以合并以消除多个副本):

- 在具有类返回类型的函数的return语句中,当表达式是具有与函数返回类型相同的cv-unqualified类型的非易失性自动对象的名称时,可以通过构造省略复制/移动操作自动对象直接进入函数的返回值

[...]

当满足复制操作的省略标准并且要通过左值指定要复制的对象时,首先执行用于选择复制的构造函数的重载决策,就好像该对象由右值指定一样.

请注意,您必须返回std::vector<T>(按价值计算),没有一个std::vector<T>&&(参照).

但为什么括号?return不是一个功能:

return theVector;
Run Code Online (Sandbox Code Playgroud)

  • @Moo:是的,但`for`*需要*括号. (6认同)

ust*_*sta 7

要添加到GMan的答案:即使您将返回类型更改为std::vector<T>(没有任何引用,否则您将获得UB),您在"1)"中的返回表达式的更改将永远不会使性能更好,但可能会使其成为有点糟糕.正如std::vector移动构造函数一样,并且返回一个本地对象,无论你写了什么,vector不会调用复制构造函数,或者.在最后两种情况下,编译器将被强制调用移动构造函数.但在第一种情况下,它可以自由地完全优化移动,如果它可以为该功能做NRVO.如果由于某种原因无法使用NRVO,那么编译器只会调用移动构造函数.所以不要改为ifreturn theVector;return static_cast<std::vector<T>&&>(theVector);return std::move(theVector)return x;return std::move(x);x 是从函数返回的本地非静态对象,否则您将阻止编译器使用其他优化机会.

  • “要添加到 GMan 的答案”,请添加评论,而不是新答案。 (2认同)

Ash*_*ain 5

移动东西的标准方法是std::move(x),而不是static_cast.AFAIK,命名返回值优化可能会在按值返回向量时启动,因此在移动语义之前它也会表现良好.

你的性能测试很好地说明了移动语义如何有利于性能:复制赋值必须复制一百万个元素,移动赋值基本上只是交换向量的内部指针,这是一个简单的单词分配或两个,只需几个周期.