簡體   English   中英

返回對對象的常量引用而不是副本

[英]Returning a const reference to an object instead of a copy

在重構一些代碼時,我遇到了一些返回 std::string 的 getter 方法。 例如這樣的事情:

class foo
{
private:
    std::string name_;
public:
    std::string name()
    {
        return name_;
    }
};

當然,吸氣劑會更好地返回一個const std::string& 當前方法正在返回一個效率不高的副本。 返回 const 引用會導致任何問題嗎?

這可能導致問題的唯一方法是調用者存儲引用而不是復制字符串,並在對象銷毀后嘗試使用它。 像這樣:

foo *pFoo = new foo;
const std::string &myName = pFoo->getName();
delete pFoo;
cout << myName;  // error! dangling reference

但是,由於您現有的函數返回一個副本,因此您不會破壞任何現有代碼。

編輯:現代 C++(即 C++11 及更高版本)支持返回值優化,因此不再反對按值返回內容。 仍然應該注意按值返回非常大的對象,但在大多數情況下應該沒問題。

實際上,另外一個問題特別是與通過引用返回一個字符串,是這樣的事實std::string提供了通過指針訪問內部const char*經由c_str()方法。 這讓我在調試過程中頭疼了好幾個小時。 例如,假設我想從 foo 獲取名稱,並將其傳遞給 JNI 以用於構造一個 jstring,以便稍后傳遞給 Java,並且name()返回的是副本而不是引用。 我可能會這樣寫:

foo myFoo = getFoo(); // Get the foo from somewhere.
const char* fooCName = foo.name().c_str(); // Woops!  foo.name() creates a temporary that's destructed as soon as this line executes!
jniEnv->NewStringUTF(fooCName);  // No good, fooCName was released when the temporary was deleted.

如果您的調用者打算做這種事情,最好使用某種類型的智能指針或常量引用,或者至少在您的 foo.name() 方法上有一個令人討厭的警告注釋標題。 我提到 JNI 是因為以前的 Java 編碼人員可能特別容易受到這種在其他方面看起來無害的方法鏈的影響。

const 引用返回的一個問題是,如果用戶編碼如下:

const std::string & str = myObject.getSomeString() ;

使用std::string返回,臨時對象將保持活動狀態並附加到 str 直到 str 超出范圍。

但是const std::string &會發生什么? 我的猜測是,當它的父對象釋放它時,我們會有一個對可能死亡的對象的 const 引用:

MyObject * myObject = new MyObject("My String") ;
const std::string & str = myObject->getSomeString() ;
delete myObject ;
// Use str... which references a destroyed object.

所以我更喜歡 const 引用返回(因為,無論如何,我只是更喜歡發送引用而不是希望編譯器優化額外的臨時文件),只要遵守以下約定:“如果你想要它超越我的對象的存在,他們在我的對象銷毀之前復制它”

std::string 的一些實現使用 copy-on-write 語義共享內存,因此按值返回幾乎與按引用返回一樣有效您不必擔心生命周期問題(運行時給你)。

如果您擔心性能,請對其進行基准測試(<= 強調不夠)!!! 嘗試兩種方法並測量增益(或缺乏)。 如果一個更好,並且您真的很在意,那就使用它。 如果沒有,那么更喜歡按價值來保護它提供的其他人提到的終生問題。

你知道他們對假設的看法...

好的,所以返回副本和返回引用之間的區別是:

  • 性能:返回引用可能會更快,也可能不會更快; 這取決於編譯器實現如何實現std::string (正如其他人指出的那樣)。 但即使您返回引用,函數調用后的賦值通常也涉及副本,如std::string name = obj.name();

  • 安全:返回引用可能會也可能不會導致問題(懸空引用)。 如果您的函數的用戶不知道他們在做什么,將引用存儲為引用並在提供對象超出范圍后使用它,那么就會出現問題。

如果你想快速安全地使用boost::shared_ptr 您的對象可以在內部將字符串存儲為shared_ptr並返回shared_ptr 這樣,就不會復制對象,並且它始終是安全的(除非您的用戶使用get()取出原始指針並在您的對象超出范圍后對其進行處理)。

我將其更改為返回 const std::string&。 如果您不更改所有調用代碼,調用者可能無論如何都會復制結果,但這不會引入任何問題。

如果您有多個線程調用 name(),則會出現一種潛在問題。 如果您返回一個引用,但隨后更改了基礎值,則調用者的值將更改。 但是現有的代碼無論如何看起來都不是線程安全的。

看看 Dima 對相關的潛在但不太可能的問題的回答。

可以想象,如果調用者真的想要副本,您可以破壞某些內容,因為他們即將更改原件並想要保留它的副本。 然而,它更有可能實際上只是返回一個 const 引用。

最簡單的方法是嘗試一下,然后測試它是否仍然有效,前提是您有某種可以運行的測試。 如果沒有,我會先專注於編寫測試,然后再繼續重構。

如果您更改為 const 引用,那么該函數的典型用法不會中斷,可能性非常大。

如果調用該函數的所有代碼都在您的控制之下,只需進行更改並查看編譯器是否會報錯。

有關系嗎? 一旦您使用現代優化編譯器,按值返回的函數將不會涉及副本,除非它們在語義上需要。

請參閱C++ lite FAQ

取決於你需要做什么。 也許您希望所有調用者在不更改類的情況下更改返回值。 如果您返回不會飛行的常量引用。

當然,下一個論點是調用者可以制作自己的副本。 但是,如果您知道如何使用該函數並且知道無論如何都會發生這種情況,那么這樣做可能會在代碼中節省一步。

我通常返回 const& 除非我不能。 QBziZ 舉例說明了這種情況。 當然 QBziZ 還聲稱 std::string 具有寫時復制語義,這在今天很少正確,因為 COW 在多線程環境中涉及大量開銷。 通過返回 const & 你把責任放在調用者身上,用他們的字符串做正確的事情。 但是,由於您正在處理已經在使用的代碼,因此您可能不應該更改它,除非分析表明復制此字符串會導致大量性能問題。 然后,如果您決定更改它,則需要徹底測試以確保沒有損壞任何東西。 希望與您合作的其他開發人員不要像 Dima 的回答那樣做粗略的事情。

返回對成員的引用公開了類的實現。 這可能會阻止更改類。 如果需要優化,可能對私有或受保護的方法有用。 C++ getter 應該返回什么

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM