33

今晚我一直在看一些我过去几天一直在处理的代码,并开始阅读移动语义,特别是 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它自动调用移动构造函数。调试到返回字符串(谢谢,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)也做了一些优化?

_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 是一个标准作业,耗时 630 毫秒。第二个结果是 0ms。这是对这些东西的良好性能测试吗?

我知道其中一些对你们很多人来说是显而易见的,但我想确保在我对我的代码进行更深入的研究之前了解语义。

提前致谢!

4

4 回答 4

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
}

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

你想用它std::move显式地移动一些东西,当它不能正常完成时,比如在你的测试中。(这似乎很好;是在 Release 中吗?您应该输出向量的内容,否则编译器会将其优化掉。)

如果您想了解右值引用,请阅读此

于 2010-11-10T19:47:24.630 回答
14
return(theVector);

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

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

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

[...]

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

请注意,您必须返回std::vector<T>( by value ),而不是( by reference )。std::vector<T>&&

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

return theVector;
于 2010-11-10T19:46:57.087 回答
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是返回的函数中的本地非静态对象,否则您将阻止编译器使用另一个优化机会。

于 2010-11-10T19:58:17.433 回答
5

移动某物的标准方式是使用std::move(x),而不是static_cast。AFAIK,命名返回值优化可能会通过按值返回向量来启动,因此它在移动语义之前也会表现良好。

您的性能测试很好地说明了移动语义对性能的好处:复制分配必须复制一百万个元素,而移动分配基本上只是交换向量的内部指针,这是一个或两个微不足道的单词分配,只需几个周期。

于 2010-11-10T19:46:17.927 回答