2009-11-11 58 views
3
返回一個臨時對象

是有可能就像這個例子代碼從函數返回一個參考:通過引用可能

string &erase_whitespace(string &text) 
{ 
    text.erase(**etc.**); 
    return text; 
} 

電話:

string text = erase_whitespace(string("this is a test")); 
cout << test; 

這是否代碼工作?在Visual C++上它不會崩潰,但看起來不對。

感謝

+1

打開所有的編譯器警告,並且VS會告訴你這是非標準的行爲,即'看起來不正確' – stijn 2009-11-11 12:36:01

+0

這只是錯誤的,因爲他將臨時綁定到非const引用參數。更改爲此應該可以:'string const&erase(string const&text)'。 – 2009-11-11 18:04:21

+0

他不能使參數成爲const ref,因爲他在函數的主體中調用了std :: string :: erase。 – 2009-11-11 18:52:20

回答

9

從§的C++ 2003標準的12.2.3(草案)

臨時對象被銷燬作爲評價全表達式(1.9),該(詞法)包含他們在那裏的點的最後一步創建。

§12.2.4:

存在其中的臨時被在不同的點比全 表達式的結尾破壞兩個上下文。 ...

§12.2.5:

的第二上下文是當引用綁定到一個暫時的。參考文件是 的臨時文件或作爲臨時文件綁定的子對象的完整對象的臨時文件在參考文獻的生命週期中保留 ,除非如下所述。 ...函數調用(5.2.2)中參數 的臨時綁定持續到包含調用的完整表達式完成。

§8.5.3.5是什麼決定何時引用必須是const類型。如果臨時是一個具有返回適當引用的轉換操作符的類的實例(這是一口),那麼可以將臨時綁定到非const引用。一個例子可能更容易理解:因爲§12.3.2.1,其中指出「A轉換功能從未使用[一個...]對象轉換爲

class Foo { 
    ... 
    operator Bar&() const; 
... 
void baz(Bar &b); 
... 
    baz(Foo()); // valid 
    baz(Bar()); // not valid 

最後一行是無效的[ ...]相同的對象類型(或對它的引用 it)「。您可以通過Bar的祖先和虛擬轉換功能進行投射。

賦值是一個表達式(第5.17節),因此代碼中的完整表達式(第1.9.12節)是賦值。這給出了以下序列(忘記的時刻,一個臨時字符串可能不能綁定到非const引用):

  1. 創建一個臨時字符串
  2. 臨時綁定到string& text說法erase_whitespace
  3. erase_whitespace做它的勝利。
  4. erase_whitespace返回到臨時
  5. 臨時基準被複制到string text
  6. 臨時被破壞。

所以在這種情況下都是猶太教。正如Mike Seymour指出的那樣,問題案例將把erase_whitespace的結果分配給參考。請注意,這可能不會造成直接問題,因爲存儲該字符串的區域可能包含與臨時銷燬前相同的數據。下一次在堆棧或堆上分配內容時,但是...

+0

感謝您的詳細解釋。 – frast 2009-11-11 13:27:21

0

更好的很可能是明確使它成爲一個堆分配瓦特/指針:

string* erase_whitespace(string* text) 
{ 
    text->erase(**etc.**); 
    return text; 
} 

string* text = erase_whitespace(new string("this is a test")); 
cout << *text; 
delete text; 
+0

我不想在代碼工作時更改它。我只是想知道它是否有效。返回分配的對象意味着客戶端代碼必須刪除它。 如果我必須改變它,我會這樣: 字符串erase_whitespace(常量字符串&text); – frast 2009-11-11 12:32:37

0

你返回參數以及功能參數需要爲const引用。在這方面,VC++是不兼容的編譯器,並允許臨時傳遞爲非常參照。

+0

與const引用我無法調用text.erase。erase是一個非const函數。我瞭解它不是標準的-compliant,但它總是按照預期在Visual C++中工作嗎? – frast 2009-11-11 12:36:31

0

雅肯定它應該工作..

因爲字符串文本= erase_whitespace(字符串( 「這是一個測試」))

由編譯器變換爲以下代碼:

串溫度( 「這是一個測試」);

string text = erase_whitespace(temp);

+0

您需要指出,在erase_whitespace返回後,temp立即退出範圍...... – Goz 2009-11-11 13:04:11

+0

我很想接受您的答案,但您在哪裏找到此信息? – frast 2009-11-11 13:09:48

+0

oO誰投了票:S – Christian 2009-11-11 13:10:50

7

如果你使用Visual C,那麼這將是最好做到以下幾點:

string erase_whitespace (const string &input) 
{ 
    string output = input.erase (...); 
    return output; 
} 

它看起來可能更糟,但編譯器,建立一個優化的版本時,可以利用Named Return Value Optimisations消除開銷通過值返回(即消除參與返回值的拷貝構造函數)。所以,它不僅看起來不錯,而且可能更有效率。

+0

這絕對是我要走的路,但它不是我的代碼,我無法將它改爲無效 – frast 2009-11-11 13:11:50

+2

順便說一下擦除無法在常量上調用字符串對象 – frast 2009-11-11 13:14:22

+0

還有改進的餘地。用我在答案中顯示的交換技巧,可以減少現代編譯器上覆制構造的數量。它也依靠編譯器來完成NRVO。 – sellibitze 2009-11-11 13:19:07

0

我同意邁克,但讓我也提到,我不認爲通過引用string周圍是一個好主意。我很確定字符串類是輕量級的,因爲它通過內部引用來存儲實際的字符數組。

+1

這種行爲在標準中沒有提到,因此不能保證。此外,字符串是可變的,這表明它們不共享存儲,除非它們使用寫時複製語義 – outis 2009-11-11 12:47:29

+0

你是對的,它的實現定義。我認爲SGI STL實現爲字符串提供了引用計數(http://www.sgi.com/tech/stl/ropeimpl.html)。 – 2009-11-11 13:24:02

4

這裏有兩個問題。

首先,返回參考被允許當然,一個例子:

struct myclass 
{ 
    myclass& foo() 
    { cout << "myclass::foo()"; return *this } 
    myclass& bar() 
    { cout << "myclass::bar()"; return *this } 
}; 
... 
myclass obj; 
obj.foo().bar(); 

第二,通過一個臨時對象到一個非const引用不是在C允許++(討論了SO很多,只是搜索):

// passing string("this is a test") is wrong 
string text = erase_whitespace(string("this is a test")); 

不幸的是,一些編譯器(如VC)允許這種行爲,雖然它不是標準的。如果你打開你的編譯器警告級別,至少應該得到一個警告。

2

我不確定你的例子有多有代表性,但你的函數看起來很好,只要你記錄它修改了傳入的對象。這是一個虛假的呼叫。我不明白爲什麼你不能只是寫:

std::string text("this is a test"); 
erase_whitespace(text); 
cout << test; 

這不正是同樣的事情,你會希望在你原來的代碼發生,即做一個std::string,剝離其空格和輸出它將std::string對象本身保留在堆棧中而不是堆中,並儘量減少副本。當然,std::string的實際存儲空間仍然堆積如山,但是當函數或塊完成時,您可以從C++中獲得處理好處。

我不明白爲什麼您需要保存該行代碼時,它轉化爲沒有實際編譯的代碼節省。

現在,如果你真的需要這個,比方說,得到塞進一個公式,通常做的事情是一樣的東西是什麼Skizz上面寫的(這在常規的C工程++,不只是視覺C):

std::string erase_whitespace (const string &input) 
{ 
    std::string output(input); 
    output.erase (...); 
    return output; 
} 

這樣做的好處是你可以傳遞const char*作爲參數,並且C++將自動創建一個臨時的std::string,因爲std::string的構造函數需要const char*。另外,這不會與傳入的對象混淆。正如Skizz指出的那樣,優化器就像這種構造返回值的類型。

+0

你說得對,我會以你指出的方式使用這個函數。實際上已經有很多代碼使用temp來以'錯誤'的方式調用函數。 – frast 2009-11-11 13:19:52

0

那麼在你賦予它的情況下工作。當您嘗試使用回報作爲參考時,您的問題就出現了。即:

string& text = erase_whitespace(string("this is a test")); 

這是完全有效的VC++代碼,但你現在很好進入「未定義」的領土。你的主要問題來自於這樣一個事實,即任何其他使用這個代碼的人都不會知道,沒有看到實現,他們不能這樣做。

所有在它的一個非常危險的位代碼只能在VC++上工作。

Skizz的回覆爲您提供的代碼可以完美地作爲一個直接替換並修復上面提到的所有問題。

+0

不幸的是,它不是一個更換的下降。如果代碼沒有用temp調用,那麼調用者期望函數返回後修改字符串對象。 – frast 2009-11-11 13:16:10

+0

好點。 – Goz 2009-11-11 13:38:50

3

它看起來不對,它錯誤,因爲非const引用不允許綁定到rvalues。在你的情況下,編譯器似乎接受它作爲擴展。你不應該依賴它。

至於可能的崩潰:沒有這個工作正常,這個編譯器擴展啓用。但是這不會:

string const& dangling_reference = 
    erase_whitespace(string("this is a test")); 

因爲該函數返回一個對將被銷燬的臨時對象的引用。如果您按值返回,則由於特殊的C++規則(臨時的生命期將延長),此行將是安全的。

另一個缺點是該函數正在改變它的參數。這可能是一個需要一個字符串並返回一個字符串的函數的意外行爲。

如果你寫的函數這種方式來提高性能,你可以嘗試這一個和衡量它:

string erase_whitespace(string text) 
{ 
    text.erase(**etc.**); 
    string ret; ret.swap(text); 
    return ret; 
} 

如果你有一個很好的編譯器,可以這樣的Elid應該表現非常好,不必要的副本。具體來說,如果函數是用右值調用的,編譯器可以通過value來接受參數。您的編譯器也可能足夠聰明以應用NRVO(命名的返回值優化)。如果是那麼聰明,下面的代碼

string foo = erase_whithespace(" blah "); 

將不會調用任何std :: string的拷貝。只有這樣才能進行交換,因爲當返回參數時,編譯器當前無法應用NRVO。

+0

在我的情況下,性能不是問題。我沒有寫這個函數。我只是想知道是否因爲可能出現的問題而改變它。 – frast 2009-11-11 13:22:47

+0

如果按值返回,您仍然無法將其綁定到非常量引用。 – UncleBens 2009-11-11 14:44:47

+0

當然不是。這部分只是關於臨時對象的生存期問題。我編輯了一下。希望這種方式更清楚。 – sellibitze 2009-11-11 15:11:47