2012-05-07 57 views
20

我在C++中有一个非常基本的问题。 如何在返回对象时避免复制?C++:避免​​使用“return”语句复制

下面是一个例子:

std::vector<unsigned int> test(const unsigned int n) 
{ 
    std::vector<unsigned int> x; 
    for (unsigned int i = 0; i < n; ++i) { 
     x.push_back(i); 
    } 
    return x; 
} 

据我所知C++是如何工作的,这个功能将创建2个载体:本地一(x)和x的副本将被返回。有没有办法避免复制? (并且我不想返回指向对象的指针,但是返回对象本身)

非常感谢。

编辑:根据第一答案额外的问题:什么是使用“移动语义”该函数的语法?

+0

移动语义:http://www2.research.att.com/~bs/C++0xFAQ.html#rval – chris

+0

它不一定会创建一个副本。 NRVO或移动语义可以防止这种情况。 –

+1

您可以依靠您的编译器来执行NRVO魔术或明确使用移动语义。 –

回答

14

该程序可以利用指定的返回值优化(NRVO)。在这里看到:http://en.wikipedia.org/wiki/Copy_elision

在C++ 11有移动建设者和分配这也便宜。你可以在这里阅读教程:http://thbecker.net/articles/rvalue_references/section_01.html

+1

需要指出的是,并非所有的编译器都会这样做,甚至那些并不是所有的编译器都会这样做。它可能仍然值得研究IFF,该对象很大,你注意到了副本,分析表明它是一个重要的瓶颈。 –

13

Named Return Value Optimization将做的工作适合你,因为编译器试图消除冗余拷贝构造函数和,而使用它析构函数调用

std::vector<unsigned int> test(const unsigned int n){ 
    std::vector<unsigned int> x; 
    return x; 
} 
... 
std::vector<unsigned int> y; 
y = test(10); 

与返回值优化:创建

  1. ý
  2. x被创建
  3. x被分配成Y
  4. x被破坏

(以防你想自己尝试一下,以便更深入的了解,看看this example of mine

,甚至更好,就像Matthieu M.指出,如果你在那里y声明的同一行中调用test,还可以防止冗余对象和冗余分配的建设,以及(x将内存中内构建y将存储):

std::vector<unsigned int> y = test(10); 

检查他的回答为更好地了解这种情况(你也会发现,这种优化不能总是被应用)。

OR,你可以修改代码以传递载体,以你的函数,参考,同时避免复制这将是语义上更正确的:

void test(std::vector<unsigned int>& x){ 
    // use x.size() instead of n 
    // do something with x... 
} 
... 
std::vector<unsigned int> y; 
test(y); 
+0

啊我明白了。事实上,我已经忽略了在分配给它之前首先构造了一个默认的'y'的事实。在这种情况下,你的事件顺序是正确的,尽管我会建议你直接初始化'y',以避免创建两个足以满足要求的对象。对不起,噪音。 –

+1

@MatthieuM:虽然我很欣赏你的观点。现在检查我的答案:) – LihO

+2

我不能给予好评两次:( –

-5

首先,你可以宣布你的返回类型是std :: vector &在这种情况下,将返回引用而不是副本。您也可以定义一个指针,在您的方法体内部构建一个指针,然后返回该指针(或该指针的副本是正确的)。

最后,许多C++编译器可以做返回值优化(http://en.wikipedia.org/wiki/Return_value_optimization)排除在某些情况下临时对象。

+4

太糟糕参考会立即被非法使用(UB)。-1不好的建议。 – Mankarse

1

引用它会工作。

Void(vector<> &x) { 

} 
31

对于RVO(返回值优化)如何工作似乎存在一些混淆。

一个简单的例子:

#include <iostream> 

struct A { 
    int a; 
    int b; 
    int c; 
    int d; 
}; 

A create(int i) { 
    A a = {i, i+1, i+2, i+3 }; 
    std::cout << &a << "\n"; 
    return a; 
} 

int main(int argc, char*[]) { 
    A a = create(argc); 
    std::cout << &a << "\n"; 
} 

而且其在ideone输出:

0xbf928684 
0xbf928684 

令人惊讶的?

实际上,这是RVO的效果:对象要返回直接构造代替在调用者。

如何?

传统上,呼叫方(main这里)会预留堆栈的返回值上的一些空间:返回槽;被调用者(在这里为create)被传递(以某种方式)返回槽的地址以将其返回值复制到。然后,被调用者为其构建结果的局部变量(如其他任何局部变量)分配自己的空间,然后在return语句中将其复制到返回位置。

当编译器从代码中推导出该变量可以直接构建到具有等效语义(as-if规则)的返回插槽中时,触发RVO。

注意,这是这样一个共同的优化,这是明确的标准白名单和编译器不担心副本可能产生的副作用(或移动)构造函数。

什么时候?

编译器是最有可能使用简单的规则,如:

// 1. works 
A unnamed() { return {1, 2, 3, 4}; } 

// 2. works 
A unique_named() { 
    A a = {1, 2, 3, 4}; 
    return a; 
} 

// 3. works 
A mixed_unnamed_named(bool b) { 
    if (b) { return {1, 2, 3, 4}; } 

    A a = {1, 2, 3, 4}; 
    return a; 
} 

// 4. does not work 
A mixed_named_unnamed(bool b) { 
    A a = {1, 2, 3, 4}; 

    if (b) { return {4, 3, 2, 1}; } 

    return a; 
} 

在(4)中,优化时不能A,因为编译器不能在建立a返回应用后者的情况下因为它可能需要其他东西(取决于布尔条件b)。

一条简单的拇指法则是这样认为:如果返还口没有其他候选人被前return语句声明

RVO应适用。

+0

+1指出,我们不仅避免复制,但建设冗余对象和冗余分配为好;) – LihO

+0

重新壳体(4),它取决于编译器(它是如何聪明)和码的细节。例如,用具体代码显示一个智能编译器可以注意到'a'的初始化没有副作用,并且该声明可以在'if'下移动。 –

+0

@ Cheersandhth.-Alf:确切地说,as-if规则仍然很明显。在一般情况下(超线程构造函数),这只能在启用LTO的情况下进行扣除。 –