2011-11-22 80 views
1

考慮下面的代碼:C++編譯器是否在按值返回時避免複製?

LargeObject getLargeObject() 
{ 
    LargeObject glo; 
    // do some initialization stuff with glo 
    return glo; 
} 

void test() 
{ 
    LargeObject tlo = getLargeObject(); 
    // do sth. with tlo; 
} 

一個簡單的編譯器將創建getLargeObject()棧上的局部LargeObject GLO和然後將其指定在測試TLO返回時,它涉及到一個複製操作()。

但是不應該有一個聰明的編譯器意識到glo將會被分配給tlo,因此只是首先使用tlo的內存來避免複製操作?導致一些(功能)像:

void getLargeObject(LargeObject &lo) 
{ 
    // do init stuff 
} 

void test() 
{ 
    LargeObject lo; 
    getLargeObject(lo); 
} 

我的猜測是,編譯器做類似的事情。但它可以一直完成嗎?有沒有不能像這樣優化的情況?我怎麼知道我的返回值是否被複制?

+7

http://en.wikipedia.org/wiki/Return_value_optimization –

+0

@OliCharlesworth:這是一個答案! –

+3

指向文章的鏈接不是答案。 –

回答

4

你的猜測是正確的。是的,有些情況下無法做到的,例如情況:

LargeObject getLargeObject() 
{ 
    LargeObject glo1, glo2; 
    // do some initialization stuff   
    if (rand() % 2) 
     return glo1; 
    return glo2; 
} 

它不能做的工作還有,因爲編譯器無法知道它是否會使用glo1或酮g102返回值。

「我怎麼知道我的返回值是否被複制?」

我能想到的兩種方法。你可以創建嘈雜的複製構造函數。也就是說,複製具有一些可檢測副作用的構造函數,如打印消息。然後,當然這是對裝配的舊看法。

+0

甚至有在這裏可以這樣做的情況。 RVO是許多平臺上ABI的一部分,這意味着調用方保留返回變量的空間並傳遞地址。一個好的編譯器會在可能的最近點在那個地方構造一個對象,並且在追溯執行路徑時,在某些情況下(例如沒有爲此目的需要考慮的ctor的副作用),它可以首先檢查如果,然後構造對象就地。無可否認,這些案例很少見,但編譯器這幾天也在改進。 – PlasmaHH

+1

更一般地說,如果函數中有多個'return',一些編譯器將無法執行RVO。當然,如果在函數中同時使用'glo1'和'glo2',則需要兩個對象,有時會返回一個,有時另一個,但編譯器無法避免複製。 –

+0

重要的是要注意,詹姆斯坎澤在他的回答中提到了它,但它在這裏不存在,實際上有兩個副本,一個從gloX到返回的臨時文件,另一個從臨時文件到對象在'test'中構建。第二個副本是PlasmaHH提到的ABI中處理的內容,並且總是*執行(在我所知道的所有平臺中),但第一個副本取決於編譯器能夠知道哪個glo1或glo2會在構造對象時返回(更多[here](http://definedbehavior.blogspot.com/2011/08/value-semantics-nrvo.html)) –

2

是的,它應該。這稱爲返回值優化(NRVO或RVO)。

+0

是的,在這種特殊情況下,它是NRVO,因爲有一個返回的命名變量。 RVO是在創建實例並在返回語句中返回的時候。 –

2

對於初學者來說,即使是天真的編譯器也不會將「分配給 tlo 」,因爲標準不允許。代碼的正式語義 涉及兩個副本(都使用複製構造函數); 第一個從glo到一個臨時返回值,第二個從這個 臨時返回值到tlo。但是,該標準正式給出了編譯器有權在這個特定的 的情況下消除這兩個副本,實際上我想所有的編譯器都這樣做。

第一個副本可以在任何時候被抑制,只要你返回一個局部變量或 臨時;但是,如果代碼中存在多個 return,則某些編譯器不會執行此操作(但是,寫入代碼的井 中絕不會出現這種情況)。

第二個副本的取消取決於您在呼叫站點構建一個新對象的事實 。如果你沒有構建一個新的對象,那麼甚至可能沒有第二個副本來壓制;例如在getLargeObject().memberFunction()等情況下爲 。但是,如果您將 分配給現有的對象,則編譯器可以執行的操作並不多;它 必須調用賦值運算符。如果分配操作員複製 ,那麼您將獲得該副本。

+0

哇,那裏有一些有趣的信息!我不確定,但如果我把它弄好。第一句和最後一句似乎與我相矛盾。或者它只是一種語言的東西? – Ben

+0

@James:*但在編寫完好的代碼中永遠不會有這樣的情況* - 哇 - 沒有政治! :) –

+0

@Ben問題是,在第一句話中,我使用_copy_來調用複製構造函數 - 我應該更加精確。在最後一句中,我以更一般的意義使用它:賦值運算符可能會「複製」大量數據。 (實際上,一個大型對象的典型賦值操作符可能會複製構造一個新實例,然後交換數據。) –

相關問題