簡體   English   中英

“const T&arg”與“T arg”

[英]“const T &arg” vs. “T arg”

以下哪個例子是聲明以下功能的更好方法?為什么?

void myFunction (const int &myArgument);

要么

void myFunction (int myArgument);

如果sizeof(T)>sizeof(void*) ,則使用const T & arg ,如果sizeof(T) <= sizeof(void*) ,則使用T arg

他們做不同的事情。 const T&使函數引用變量。 另一方面, T arg 將調用對象的復制構造函數並傳遞副本 如果復制構造函數不可訪問(例如它是private ),則T arg將不起作用:

class Demo {
    public: Demo() {} 
    private: Demo(const Demo& t) { } 
};

void foo(Demo t) { }

int main() {
    Demo t;
    foo(t); // error: cannot copy `t`.
    return 0;
}

對於像原始類型這樣的小 (其中所有重要的是對象的內容,而不是實際的引用標識;比如,它不是句柄或其他東西),通常首選T arg 對於無法復制和/或保留引用標識的大對象和對象很重要(無論大小如何),首選傳遞引用。

T arg另一個優點是,由於它是副本,被調用者不能惡意改變原始值。 它可以像任何局部變量一樣自由地改變變量來完成它的工作。

取自Move構造函數 我喜歡簡單的規則

  1. 如果函數打算將參數更改為副作用,則通過引用/指針將其作為非const對象。 例:

     void Transmogrify(Widget& toChange); void Increment(int* pToBump); 
  2. 如果函數不修改其參數且參數是基本類型,則按值取值。 例:

     double Cube(double value); 
  3. 除此以外

    3.1。 如果函數始終在其中復制其參數,請按值取值。

    3.2。 如果函數從不復制其參數,則通過引用const來獲取它。

    3.3。 我添加 :如果該功能有時復制,那么決定直覺:如果副本幾乎總是完成,那么按值進行。 如果復制完成了一半的時間,那么請以安全的方式參考const。

在您的情況下,您應該通過值獲取int,因為您不打算修改參數,並且參數是基本類型。 我認為“原始類型”可以是非類型類型,也可以是沒有用戶定義的復制構造函數的類型,其中sizeof(T)只有幾個字節。

有一個流行的建議,聲明應該根據你要傳遞的類型的實際大小來選擇傳遞方法(“按值”與“通過const引用”)。 即使在這個討論中,你也有一個標記為“正確”的答案。

實際上,根據類型的大小做出決定不僅是不正確的,這是一個主要且相當明顯的設計錯誤,表明嚴重缺乏對良好編程實踐的直覺/理解。

基於對象的實際依賴於實現的物理大小的決策必須盡可能多地留給編譯器。 試圖通過對傳遞方法進行硬編碼來“調整”代碼到這些大小,這對於100個中的99個案例來說是完全適得其反的浪費。(是的,確實如此,在C ++語言的情況下,編譯器不會有足夠的自由可以互換地使用這些方法 - 在一般情況下它們在C ++中並不是真的可以互換。雖然如果有必要,可以通過模板元編程實現適當的基於大小的[半]自動傳遞方法選擇;但這是一個不同的故事)。

在“手動”編寫代碼時選擇傳遞方法的更有意義的標准可能聽起來如下:

  1. 當您傳遞一個原子的,單一的,不可分割的實體時,更喜歡傳遞“by value”,例如任何類型的單個非聚合值 - 數字,指針,迭代器。 請注意,例如,迭代器是邏輯級別的單一值。 因此,不管它們的實際大小是否大於sizeof(void *),都希望按值傳遞迭代器。 (STL實現就是這樣,BTW)。

  2. 當您傳遞任何類型的聚合值時,更喜歡傳遞“by const reference”。 即在邏輯級別上暴露出明顯“復合”性質的值,即使其大小不大於sizeof(void *)。

兩者之間的分離並不總是很清楚,但是所有這些建議總是如此。 此外,分離成“原子”和“復合”實體可能取決於您的設計的具體情況,因此決策實際上可能因設計而異。

請注意,此規則可能會產生與本討論中提到的所謂“正確”基於大小的方法不同的決策。

作為一個例子,它傾向於觀察,基於大小的方法將建議您手動硬編碼不同類型的迭代器的不同傳遞方法,具體取決於它們的物理大小。 這使得基於大小的方法是多么虛偽是特別明顯的。

再一次,良好的編程實踐所依據的基本原則之一是避免將您的決策建立在平台的物理特性上(盡可能多)。 相反,您的決策必須基於程序中實體的邏輯和概念屬性(盡可能多)。 傳遞“按價值”或“按參考”的問題在這里也不例外。


在C ++ 11中,將移動語義引入語言產生了不同參數傳遞方法的相對優先級的顯着轉變。 在某些情況下,按值傳遞復雜對象可能變得完全可行

是否應將C ++ 11中的所有/大多數setter函數編寫為接受通用引用的函數模板?

與流行的和長期存在的信念相反,即使你傳遞一個大型物體,傳遞const引用也不一定更快。 您可能想閱讀Dave Abrahams最近關於這個主題的文章

編輯:(主要是回應Jeff Hardy的評論):在最大數量的情況下,通過const引用傳遞可能是“最安全”的選擇 - 但這並不意味着它總是最好的事情。 但是,為了理解這里討論的是什么,你真的需要仔細閱讀Dave的整篇文章,因為它是相當技術性的,其結論背后的推理並不總是直觀明顯(你需要理解做出明智選擇的理由) )。

通常對於內置類型,您只需按值傳遞即可。 他們是小型的。

對於用戶定義的類型(或模板,當你沒有要傳遞的東西時)更喜歡const&。 引用的大小可能小於類型的大小。 並且它不會產生額外的副本(不調用復制構造函數)。

嗯,是的......關於效率的其他答案都是正確的。 但是這里還有其他一些重要的事情 - 按值傳遞一個類會創建一個副本,因此會調用復制構造函數。 如果你在那里做過花哨的東西,那么使用引用是另一個原因。

對於標量類型(如int,double等),對const T的引用不值得輸入。經驗法則是應該通過ref-to-const接受類類型。 但對於迭代器(可能是類類型),我們經常會例外。

在通用代碼中,您應該在大多數情況下寫“T const&”以保證安全。 還可以使用boost的調用特性來選擇最有前途的參數傳遞類型。 據我所知,它基本上使用ref-to-const作為類類型,並使用pass-by-value作為標量類型。

但是,在某些情況下,您可能希望按值接受參數,而不管創建副本的成本是多少。 請參閱Dave的文章“想要速度?使用價值傳遞!”

對於像int,double和char *這樣的簡單類型,按值傳遞它是有意義的。 對於更復雜的類型,我使用const T&除非有特殊原因不這樣做。

傳遞4 - 8字節參數的成本與您可以獲得的一樣低。 您不通過傳遞參考購買任何東西。 對於較大的類型,按值傳遞它們可能很昂貴。

對於int來說它沒有任何區別,因為當你使用引用時,仍然必須傳遞內存地址,並且內存地址(void *)通常大約是整數的大小。

對於包含大量數據的類型,它變得更加高效,因為它避免了必須復制數據的巨大開銷。

那么兩者之間的差異對於整數來說並不是很重要。

但是,當使用較大的結構(或對象)時,您使用的第一個方法(通過const引用)可以訪問該結構而無需復制它。 第二種情況傳遞值將實例化一個與參數具有相同值的新結構。

在這兩種情況下,您都會在調用者中看到此信息

myFunct(item);

對於調用者,myFunct不會更改項目,但是按引用傳遞不會產生創建副本的成本。

在C ++中通過引用/值的類似問題有一個非常好的答案

它們之間的區別在於,傳遞一個int(被復制),一個使用現有的int。 因為它是一個const引用,所以它不會被改變,所以它的工作方式大致相同。 這里最大的區別是函數可以在本地改變int的值,但不能改變const引用。 (我想有些白痴可以用const_cast<>做同樣的事情,或者至少嘗試一下。)對於較大的對象,我可以想到兩個不同之處。

首先,一些對象根本無法復制, auto_ptr<>和包含它們的對象就是一個明顯的例子。

其次,對於大而復雜的對象,通過const引用傳遞比復制更快。 它通常不是什么大問題,但通過const引用傳遞對象是一個有用的習慣。

要么工作正常。 不要浪費你的時間擔心這些東西。

唯一可能產生影響的是當類型是一個大型結構時,傳遞堆棧可能會很昂貴。 在這種情況下,將arg作為指針或引用傳遞(稍微)更有效。

傳遞對象時會出現問題。 如果傳遞值,則將調用復制構造函數。 如果尚未實現,則將該對象的淺表副本傳遞給該函數。

為什么這是個問題? 如果您有指向動態分配內存的指針,則可以在調用副本的析構函數時釋放(當對象離開函數的作用域時)。 然后,當你重新打電話給你的析構函數時,你將獲得雙倍免費。

道德:寫你的副本構造函數。

暫無
暫無

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

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