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

时间:2010-11-10 19:37:19

标签: c++ c++11 move-semantics c++-standard-library

今晚我一直在看一些我过去几天一直在研究的代码,并开始阅读移动语义,特别是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

鉴于“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

这是对的吗?这样做有什么陷阱吗?

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

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

(为简洁而省略_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;

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

我知道其中一些对很多人来说是显而易见的,但我想确保在我对我的代码进行开拓之前理解语义。

提前致谢!

4 个答案:

答案 0 :(得分:36)

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

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

    return local; // oops
    return std::move(local); // also oops
}

你应该按照你在第二个看到的那样做:

// 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
}

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

您希望{em}明确移动某些内容时使用std::move来移动某些内容,就像在测试中一样。 (这似乎很好;是Release中的那个吗?你应该输出向量的内容,否则编译器会优化它。)

如果您想了解右值参考,read this

答案 1 :(得分:14)

return(theVector);

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

  

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

     

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

     

[...]

     

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

请注意,您必须返回std::vector<T>按值), a std::vector<T>&&引用 )。

但为什么括号? return不是函数:

return theVector;

答案 2 :(得分:7)

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

答案 3 :(得分:5)

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

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