从具有移动语义或返回值优化的函数返回值,但不返回复制构造函数

时间:2013-12-10 01:09:58

标签: c++ c++11

有没有一种很好的方法从C ++中的函数返回一个值,我们保证不会调用复制构造函数?返回值优化或移动构造函数都可以。例如,使用以下代码

#include <iostream>

struct Foo {
private:
    // Disallow the copy and default constructor as well as the assignment
    // operator
    Foo();
    Foo(Foo const & foo);
    Foo & operator = (Foo const & foo);

public:                         
    // Store a little bit of data
    int data;                   
    Foo(int const & data_) : data(data_) { }

    // Write a move constructor 
    Foo(Foo && foo) {           
        std::cout << "Move constructor" << std::endl;
        data=foo.data;          
    }                           
};                              

// Write a function that creates and returns a Foo
Foo Bar() {                     
    Foo foo(3);                 
    return foo;                 
}                               

// See if we can mix things up  
Foo Baz(int x) {                
    Foo foo2(2);                
    Foo foo3(3);                
    return x>2 ? foo2 : foo3;   
}                               

int main() {                    
    // This is using the return value optimization (RVO)
    Foo foo1 = Bar();           
    std::cout << foo1.data << std::endl;

    // This should make the RVO fail 
    Foo foo2 = Baz(3);
    std::cout << foo2.data << std::endl;
}

我们有编译错误

$ make
g++ -std=c++11 test01.cpp -o test01
test01.cpp: In function 'Foo Baz(int)':
test01.cpp:10:5: error: 'Foo::Foo(const Foo&)' is private
test01.cpp:35:25: error: within this context
make: *** [all] Error 1

因为复制构造函数是私有的。现在,如果我们将Baz函数修改为

// See if we can mix things up
Foo Baz(int x) {
    Foo foo2(2);
    Foo foo3(3);
    return std::move(x>2 ? foo2 : foo3);
}
事实上,我们确实正常运行。但是,这似乎排除了RVO的使用。如果我们必须保证不调用复制构造函数,是否有更好的方法来构造这些函数?

2 个答案:

答案 0 :(得分:3)

来自C ++标准:

  

[class.copy] / 31:当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数具有副作用。 ......在下列情况下(允许合并以消除多份副本),允许复制/移动操作(称为复制省略)的省略:

     
      
  • 在具有类返回类型的函数的return语句中,当表达式是a的名称时   非易失性自动对象(函数或catch子句参数除外)具有相同的cv-   作为函数返回类型的非限定类型,可以通过构造省略复制/移动操作   自动对象直接进入函数的返回值
  •   

由于x > 2 ? foo2 : foo3不是自动对象的名称,因此不允许复制省略。

答案 1 :(得分:1)

有趣的是,您的示例已在n1377中解决:

  

有了这种语言功能,移动/复制省略,尽管仍然如此   重要的是,不再是关键。 NRVO有一些功能   允许,但可能非常难以实施。对于   例如:

A
f(bool b)
{
    A a1, a2;
    // ...
   return b ? a1 : a2;
}
     

介于难以决定是否存在之间   在呼叫者的首选位置构造a1或a2。使用A的举动   构造函数(而不是复制构造函数)将a1或a2发送回   来电是最好的解决方案。

     

我们可以要求operator +的作者明确请求   移动语义。但重点是什么?目前的语言   已经允许这个副本的省略,所以编码器已经可以了   不依赖于当地的破坏秩序,也不能依靠它   复制构造函数被调用。自动本地即将到来   无论如何,它在概念上被破坏了,所以它非常像“rvalue-like”。此举   除非通过测量性能或计算副本,否则无法检测到   (无论如何都可以省略。)

     

请注意,此语言添加允许移动,但不可复制   自移动以来,由值返回的对象(例如move_ptr)   找到并使用(或省略)构造函数而不是不可访问的构造函数   复制构造函数。

他们解决这个问题的例子(支持移动语义)是:

// Or just call std::move
// return x>2 ? static_cast<Foo&&>(foo2) : static_cast<Foo&&>(foo3);
return static_cast<Foo&&>(x>2 ? foo2 : foo3);
  

这种隐式演员产生的逻辑导致自动   “移动语义”从最好到最差的层次结构:

If you can elide the move/copy, do so (by present language rules)
Else if there is a move constructor, use it
Else if there is a copy constructor, use it
Else the program is ill formed

或者正如Xeo所提到的,你可以这样构建它:

Foo Baz(int x) {                
    Foo foo2(2);                
    Foo foo3(3);                
    if (x > 2)
        return foo2;
    else
        return foo3;
}        

您已经在OP中提供了一个示例,但该标准提供了一个用于删除移动/复制构造函数的示例(同样适用):

class Thing {
public:
    Thing() { }
    ~Thing() { }
    Thing(Thing&& thing) {
        std::cout << "hi there";
    }
};
Thing f() {
    Thing t;
    return t;
}
Thing t2 = f();
// does not print "hi there"

但是如果同时提供移动和复制构造函数,那么移动构造函数似乎是首选。