2012-05-07 59 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的情況下進行扣除。 –