【问题标题】:How to return a member of a local object如何返回本地对象的成员
【发布时间】:2015-10-09 05:01:06
【问题描述】:

考虑以下代码:

struct Foo
{
    Foo() { cout << "Foo()\n"; }
    ~Foo() { cout << "~Foo()\n"; }
    Foo(Foo&) { cout << "Foo(Foo&)\n"; }
    Foo(Foo&&) { cout << "Foo(Foo&&)\n"; }

    int d;
};

struct Bar
{
    Foo bigData;
    void workOnBigData() { /*...*/ }
}

Foo getBigData()
{
    Bar b;
    b.workOnBigData();
    return b.bigData;
}

就复制省略/移动语义而言,实现getBigData() 的最佳方式是什么?在这个实现中,编译器似乎不允许移动bigData。我测试了以下功能:

Foo f()
{
    Foo foo;
    return foo;  // RVO
}

Foo g()
{
    Bar b;
    return b.bigData;  // Copy
}

Foo h()
{
    Bar b;
    auto r = move(b.bigData);
    return r;  // Move
}

您能解释一下这些实现的结果,并展示返回本地对象成员的最有效方法吗?

【问题讨论】:

  • 对于命名返回值优化,调用者必须在调用函数之前保留一些堆栈空间。然后,该内存将用于构造被调用者的本地对象,返回语句变为无操作。因此,NRVO 不能应用于子对象,只能应用于完整对象(它有额外的限制)。
  • Automatic move on return 是一种优化,它改变了程序的可观察行为。它是保守地引入的,即仅适用于允许应用 NRVO 的情况(但编译器不能)和其他一些极端情况。如果没有介绍,那么人们可能经常写return std::move(x);,即使x可以通过NRVO返回;但是move(x) 阻止了 NRVO,所以这将是一种悲观(对于仅移动类型,return x 还必须检查移动 ctor,如果应应用 NRVO)。
  • @dyp 这是否意味着,当 NRVO 无法应用时,return std::move(x) 应用于强制执行返回移动(在这些情况下无法自动完成)?
  • 哦,由于CWG 1579,C++11 中的“极端情况”在 C++14 中得到了放松。 -- “当 NRVO 不能应用时” 取决于你所说的“不能应用”。如果它可能不会被应用,那么你应该move。如果它可能被应用,请不要move。但我猜 C++14 规则更容易:move 除非你的 return 语句是 return name; 其中name 是本地(非静态)变量或函数参数。

标签: c++ c++11 move-semantics rvo


【解决方案1】:

有很多方法可以避免额外的副本,最接近您的代码的一种方法是恕我直言:

Foo getBigData()
{
    Foo ret; // do a cheap initialization
    Bar b;
    b.workOnBigData();

    std::swap(ret, b.bigData); // 'steal' the member here

    return ret; // NRVO can apply
}

同样可以通过move构造返回对象来实现

Foo getBigData()
{
    Bar b;
    b.workOnBigData();     
    Foo ret(std::move(b.bigData)); // these two lines are equivalent to
    return ret;                    // return std::move(b.bigData); 
}

【讨论】:

    【解决方案2】:

    我认为这个问题的答案Why isn't the copy constructor elided here? 对回答您的问题非常有用。您的示例中未使用复制省略

    Foo getBigData()
    {
        Bar b;
        b.workOnBigData();
        return b.bigData;
    }
    

    由于此要求未完成(http://en.cppreference.com/w/cpp/language/copy_elision):

    return 语句的表达式是非易失对象的名称 具有自动存储期限... 并且具有相同的类型(忽略顶级 cv-qualification) 函数的返回类型,则省略复制/移动

    在您的示例中,Bar 是一个具有自动存储持续时间的变量,但您返回 Foo。如果您更改 Bar 类,编译器将开始使用复制省略:

    #include <iostream>
    #include <typeinfo>
    
    using namespace std;
    
    struct Foo
    {
      Foo() { cout << "Foo()\n"; }
      ~Foo() { cout << "~Foo()\n"; }
      Foo(const Foo&) { cout << "Foo(Foo&)\n"; }
      Foo(Foo&&) { cout << "Foo(Foo&&)\n"; }
    
      int d;
    };
    
    struct Bar
    {
      Foo bigData;
      void workOnBigData() { /*...*/ }
    };
    
    struct Bar2
    {
      void workOnBigData(Foo&) { /*...*/ }
    };
    
    Foo getBigData()
    {
        Bar b;
        b.workOnBigData();
        return b.bigData;
    }
    
    Foo getBigData2()
    {
        Foo f;
        Bar2 b;
        b.workOnBigData(f);
        return f;
    }
    
    int main()
    {
      {
        Foo f = getBigData();
      }
     cout << "---" << endl;
    
      {
        Foo f = getBigData2();
      }
    }
    
    #include <iostream>
    #include <typeinfo>
    
    using namespace std;
    
    struct Foo
    {
      Foo() { cout << "Foo()\n"; }
      ~Foo() { cout << "~Foo()\n"; }
      Foo(const Foo&) { cout << "Foo(Foo&)\n"; }
      Foo(Foo&&) { cout << "Foo(Foo&&)\n"; }
    
      int d;
    };
    
    struct Bar
    {
      Foo bigData;
      void workOnBigData() { /*...*/ }
    };
    
    struct Bar2
    {
      void workOnBigData(Foo&) { /*...*/ }
    };
    
    Foo getBigData()
    {
        Bar b;
        b.workOnBigData();
        return b.bigData;
    }
    
    Foo getBigData2()
    {
        Foo f;
        Bar2 b;
        b.workOnBigData(f);
        return f;
    }
    
    int main()
    {
      {
        Foo f = getBigData();
      }
     cout << "---" << endl;
    
      {
        Foo f = getBigData2();
      }
    }
    

    这是它的输出:

    $ ./a.out     
    Foo()
    Foo(Foo&)
    ~Foo()
    ~Foo()
    ---
    Foo()
    ~Foo()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-03
      • 2018-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多