簡體   English   中英

何時使用引用與指針

[英]When to use references vs. pointers

我了解指針與引用的語法和一般語義,但是我應該如何決定在 API 中何時或多或少地適合使用引用或指針?

當然,有些情況需要其中一種( operator++需要引用參數),但總的來說,我發現我更喜歡使用指針(和 const 指針),因為語法清楚地表明變量正在以破壞性方式傳遞。

例如在以下代碼中:

void add_one(int& n) { n += 1; }
void add_one(int* const n) { *n += 1; }
int main() {
  int a = 0;
  add_one(a); // Not clear that a may be modified
  add_one(&a); // 'a' is clearly being passed destructively
}

有了指針,發生的事情總是(更)明顯,所以對於清晰度是一個大問題的 API 等,指針不比引用更合適嗎? 這是否意味着只能在必要時使用引用(例如operator++ )? 其中一個是否存在性能問題?

編輯(過時):

除了允許 NULL 值和處理原始 arrays 之外,似乎選擇取決於個人喜好。 我已經接受了下面引用Google 的 C++ 樣式指南的答案,因為它們提出了“引用可能令人困惑,因為它們具有值語法但指針語義。”的觀點。

由於清理不應該是 NULL 的指針 arguments 需要額外的工作(例如add_one(0)將調用指針版本並在運行時中斷),從可維護性的角度來看,使用存在 ZA8CFDE6331BD59EB26AC96F8 的引用是有意義的失去語法清晰度是一種恥辱。

盡可能使用參考,必須使用指針。

避免指針,直到你不能。

原因是與任何其他構造相比,指針使事情更難理解/閱讀,更不安全和更危險的操作。

所以經驗法則是只有在沒有其他選擇的情況下才使用指針。

例如,當 function 在某些情況下可以返回nullptr並且假定它會返回時,返回指向 object 的指針是一個有效選項。 也就是說,更好的選擇是使用類似於std::optional的東西(需要 C++17;在此之前,有boost::optional )。

另一個示例是使用指向原始 memory 的指針進行特定的 memory 操作。 這應該在代碼的非常狹窄的部分隱藏和本地化,以幫助限制整個代碼庫的危險部分。

在您的示例中,使用指針作為參數是沒有意義的,因為:

  1. 如果您提供nullptr作為參數,那么您將進入 undefined-behaviour-land;
  2. 參考屬性版本不允許(沒有容易發現的技巧)1的問題。
  3. 參考屬性版本更易於用戶理解:您必須提供有效的 object,而不是 null。

如果 function 的行為必須在有或沒有給定 object 的情況下工作,那么使用指針作為屬性表明您可以將nullptr作為參數傳遞,對於 ZC1C425268E68385D1AB5074C17A 來說很好。 這是用戶和實現之間的一種契約。

性能完全相同,因為引用在內部實現為指針。 因此,您無需擔心這一點。

關於何時使用引用和指針沒有普遍接受的約定。 在少數情況下,您必須返回或接受引用(例如,復制構造函數),但除此之外,您可以隨意做任何事情。 我遇到的一個相當常見的約定是在參數必須引用現有 object 時使用引用,並且當 NULL 值正常時使用指針。

一些編碼約定(如Google 的)規定應該始終使用指針或 const 引用,因為引用有一些不明確的語法:它們有引用行為但有值語法。

來自C++ FAQ Lite -

盡可能使用引用,必要時使用指針。

每當您不需要“重新安裝”時,引用通常優於指針。 這通常意味着引用在類的公共接口中最有用。 引用通常出現在 object 的皮膚上,而指針則出現在內部。

上述情況的例外是函數的參數或返回值需要“哨兵”引用——一個不引用 object 的引用。 這通常最好通過返回/獲取一個指針,並賦予 NULL 指針這個特殊意義(引用必須始終為對象別名,而不是取消引用的 NULL 指針)來完成。

注意:舊行 C 程序員有時不喜歡引用,因為它們提供的引用語義在調用者的代碼中並不明確。 然而,在一些 C++ 經驗之后,人們很快意識到這是一種信息隱藏形式,它是一種資產而不是一種負債。 例如,程序員應該用問題的語言而不是機器的語言編寫代碼。

我的經驗法則是:

  • 將指針用於傳出或輸入/輸出參數。 因此可以看出該值將被更改。 (您必須使用&
  • 如果 NULL 參數是可接受的值,則使用指針。 (如果它是傳入參數,請確保它是const
  • 如果傳入參數不能是 NULL 並且不是原始類型 ( const T& ),則使用對傳入參數的引用。
  • 返回新創建的 object 時使用指針或智能指針。
  • 使用指針或智能指針作為結構或 class 成員而不是引用。
  • 使用別名的引用(例如int &current = someArray[i]

無論您使用哪一個,如果它們不明顯,請不要忘記記錄您的函數及其參數的含義。

免責聲明:除了引用不能是 NULL 也不能“反彈”(意味着他們不能改變 object 的別名)之外,這真的歸結為一個品味問題,所以我不會說“這個更好”。

也就是說,我不同意你在帖子中的最后陳述,因為我認為代碼不會因為引用而失去清晰度。 在你的例子中,

add_one(&a);

可能比

add_one(a);

因為您知道 a 的值很可能會發生變化。 另一方面,function 的簽名

void add_one(int* const n);

也有點不清楚:n 是單個 integer 還是一個數組? 有時您只能訪問(文檔不足的)標頭和簽名,例如

foo(int* const a, int b);

乍一看並不容易解釋。

恕我直言,當不需要(重新)分配或重新綁定(在前面解釋的意義上)時,引用與指針一樣好。 此外,如果開發人員只使用 arrays 的指針,函數簽名就不會那么模糊。 更不用說運算符語法在引用中更具可讀性這一事實。

就像其他人已經回答的那樣:始終使用引用,除非變量NULL / nullptr確實是有效的 state。

John Carmack 在這個問題上的觀點是相似的:

NULL 指針是 C/C++ 中最大的問題,至少在我們的代碼中是這樣。 將單個值同時用作標志和地址會導致數量驚人的致命問題。 C++ 引用應盡可能優於指針; 雖然引用“實際上”只是一個指針,但它具有非 NULL 的隱含契約。 當指針變為引用時執行 NULL 檢查,然后您可以忽略此問題。

http://www.altdevblogaday.com/2011/12/24/static-code-analysis/

編輯 2012-03-13

用戶Bret Kuhns正確地評論道:

C++11 標准已經定稿。 我認為是時候在這個線程中提到大多數代碼應該可以完美地結合引用、shared_ptr 和 unique_ptr。

確實如此,但問題仍然存在,即使用智能指針替換原始指針也是如此。

例如, std::unique_ptrstd::shared_ptr都可以通過它們的默認構造函數構造為“空”指針:

...意味着在不驗證它們不是空的情況下使用它們可能會導致崩潰,這正是 J. Carmack 討論的全部內容。

然后,我們有一個有趣的問題“我們如何將智能指針作為 function 參數傳遞?”

Jon對 C++ 問題的回答- 傳遞對 boost::shared_ptr 的引用,以下評論表明,即便如此,通過復制或引用傳遞智能指針並不像人們想要的那樣明確(我贊成自己“ by-reference”默認情況下,但我可能是錯的)。

這不是品味問題。 這里有一些明確的規則。

如果你想在 scope 中引用一個靜態聲明的變量,然后使用 C++ 引用,這將是非常安全的。 這同樣適用於靜態聲明的智能指針。 通過引用傳遞參數就是這種用法的一個例子。

如果你想引用 scope 中的任何東西,它比聲明它的 scope 更寬,那么你應該使用引用計數的智能指針來確保它是完全安全的。

為了語法方便,您可以使用引用來引用集合的元素,但這並不安全; 該元素可以隨時刪除。

要安全地保存對集合元素的引用,您必須使用引用計數智能指針。

任何性能差異都會非常小,以至於無法證明使用不太清楚的方法是合理的。

首先,沒有提到引用通常優越的一種情況是const引用。 對於非簡單類型,傳遞const reference可避免創建臨時引用,並且不會引起您擔心的混淆(因為未修改值)。 在這里,強迫一個人傳遞一個指針會導致你擔心的非常混亂,因為看到地址被占用並傳遞給 function 可能會讓你認為值改變了。

無論如何,我基本上同意你的看法。 當 function 正在做的事情不是很明顯時,我不喜歡函數引用來修改它們的值。 在這種情況下,我也更喜歡使用指針。

當您需要返回復雜類型的值時,我傾向於使用引用。 例如:

bool GetFooArray(array &foo); // my preference
bool GetFooArray(array *foo); // alternative

在這里,function 名稱清楚地表明您正在獲取數組中的信息。 所以沒有混淆。

引用的主要優點是它們總是包含一個有效值,比指針更干凈,並且支持多態而不需要任何額外的語法。 如果這些優點都不適用,則沒有理由更喜歡引用而不是指針。

要記住的幾點:

  1. 指針可以是NULL ,引用不能是NULL

  2. 引用更容易使用,當我們不想更改值並且只需要 function 中的引用時,可以使用const作為引用。

  3. 指針與*一起使用,而引用與&一起使用。

  4. 當需要指針算術運算時使用指針。

  5. 您可以將指針指向 void 類型int a=5; void *p = &a; int a=5; void *p = &a; 但不能引用 void 類型。

指針與參考

void fun(int *a)
{
    cout<<a<<'\n'; // address of a = 0x7fff79f83eac
    cout<<*a<<'\n'; // value at a = 5
    cout<<a+1<<'\n'; // address of a increment by 4 bytes(int) = 0x7fff79f83eb0
    cout<<*(a+1)<<'\n'; // value here is by default = 0
}
void fun(int &a)
{
    cout<<a<<'\n'; // reference of original a passed a = 5
}
int a=5;
fun(&a);
fun(a);

判斷何時使用什么

指針:用於數組、鏈接列表、樹實現和指針算法。

參考:在 function 參數和返回類型。

維基復制 -

這樣做的結果是,在許多實現中,通過引用對具有自動或 static 生命周期的變量進行操作,盡管在語法上類似於直接訪問它,但可能涉及成本高昂的隱藏取消引用操作。 引用是 C++ 的一個語法上有爭議的特性,因為它們掩蓋了標識符的間接級別; 也就是說,與 C 代碼中指針通常在句法上突出的代碼不同,在 C++ 代碼的大塊中,如果 object 是被訪問的變量或全局指針的引用,則可能不會立即明顯其他一些位置,特別是如果代碼混合了引用和指針。 這方面會使編寫不佳的 C++ 代碼更難閱讀和調試(請參閱別名)。

我 100% 同意這一點,這就是為什么我認為只有在你有充分理由這樣做時才應該使用參考。

盡可能使用參考”規則存在問題,如果您想保留參考以供進一步使用,就會出現問題。 為了舉例說明這一點,假設您有以下課程。

class SimCard
{
    public:
        explicit SimCard(int id):
            m_id(id)
        {
        }

        int getId() const
        {
            return m_id;
        }

    private:
        int m_id;
};

class RefPhone
{
    public:
        explicit RefPhone(const SimCard & card):
            m_card(card)
        {
        }

        int getSimId()
        {
            return m_card.getId();
        }

    private:
        const SimCard & m_card;
};

起初,在RefPhone(const SimCard & card)構造函數中通過引用傳遞參數似乎是一個好主意,因為它可以防止將錯誤/空指針傳遞給構造函數。 它以某種方式鼓勵在堆棧上分配變量並從 RAII 中受益。

PtrPhone nullPhone(0);  //this will not happen that easily
SimCard * cardPtr = new SimCard(666);  //evil pointer
delete cardPtr;  //muahaha
PtrPhone uninitPhone(cardPtr);  //this will not happen that easily

但隨后臨時工來摧毀你的幸福世界。

RefPhone tempPhone(SimCard(666));   //evil temporary
//function referring to destroyed object
tempPhone.getSimId();    //this can happen

因此,如果您盲目地堅持引用,您會在傳遞無效指針的可能性與存儲對已破壞對象的引用的可能性之間進行權衡,這具有基本相同的效果。

編輯:請注意,我堅持“盡可能使用參考,必須使用指針。避免使用指針,直到不能使用”的規則。 來自最受好評和接受的答案(其他答案也建議如此)。 盡管應該很明顯,但示例並不是要表明此類引用是不好的。 然而,它們可能會被濫用,就像指針一樣,它們會給代碼帶來自己的威脅。


指針和引用之間有以下區別。

  1. 在傳遞變量時,按引用傳遞看起來像按值傳遞,但具有指針語義(類似於指針)。
  2. 引用不能直接初始化為0(null)。
  3. 引用(reference,未引用的對象)不能被修改(相當於“* const”指針)。
  4. const 引用可以接受臨時參數。
  5. 本地 const 引用延長臨時對象的生命周期

考慮到這些,我目前的規則如下。

  • 對將在 function scope 中本地使用的參數使用參考。
  • 當 0 (null) 是可接受的參數值或您需要存儲參數以供進一步使用時使用指針。 如果 0 (null) 是可接受的,我將在參數中添加“_n”后綴,使用受保護的指針(如 Qt 中的 QPointer)或僅記錄它。 您還可以使用智能指針。 與普通指針相比,您必須更加小心共享指針(否則您最終可能會因設計 memory 泄漏和責任混亂而告終)。

以下是一些指導方針。

function 使用傳遞的數據而不修改它:

  1. 如果數據 object 很小,例如內置數據類型或小型結構,則按值傳遞。

  2. 如果數據 object 是一個數組,請使用指針,因為這是您唯一的選擇。 使指針成為指向 const 的指針。

  3. 如果數據 object 是一個大小合適的結構,請使用 const 指針或 const 引用來提高程序效率。您可以節省復制結構或 class 設計所需的時間和空間。 將指針或引用設為常量。

  4. If the data object is a class object, use a const reference.The semantics of class design often require using a reference, which is the main reason C++ added this feature.Thus, the standard way to pass class object arguments is by reference.

A function 修改調用 function 中的數據:

1.如果數據object是內置數據類型,使用指針。 如果你發現像 fixit(&x) 這樣的代碼,其中 x 是一個 int,很明顯這個 function 打算修改 x。

2.如果數據 object 是數組,請使用您唯一的選擇:指針。

3.如果數據object是結構體,使用引用或指針。

4.如果數據 object 是 class object,請使用參考。

當然,這些只是指導方針,可能有做出不同選擇的原因。 例如,cin 使用基本類型的引用,因此您可以使用 cin >> n 代替 cin >> &n。

您正確編寫的示例應如下所示

void add_one(int& n) { n += 1; }
void add_one(int* const n)
{
  if (n)
    *n += 1;
}

這就是為什么如果可能的話,參考是可取的......

引用更清晰,更易於使用,並且它們在隱藏信息方面做得更好。 但是,不能重新分配引用。 如果您需要先指向一個 object,然后再指向另一個,則必須使用指針。 引用不能是 null,因此如果有任何可能存在問題的 object 可能是 null,則不得使用引用。 您必須使用指針。 如果您想自己處理 object 操作,即如果您想在堆上而不是在堆棧上為 object 分配 memory 空間,則必須使用指針

int *pInt = new int; // allocates *pInt on the Heap

在我的實踐中,我個人采用了一條簡單的規則——對可復制/可移動的原語和值使用引用,對生命周期長的對象使用指針。

對於節點示例,我肯定會使用

AddChild(Node* pNode);

把我的一角錢放進去。我剛剛做了一個測試。 一個狡猾的人。 與使用引用相比,我只是讓 g++ 使用指針創建同一個小程序的匯編文件。 查看 output 時,它們完全相同。 除了符號命名。 所以看性能(在一個簡單的例子中)沒有問題。

現在關於指針與引用的主題。 恕我直言,我認為清晰高於一切。 一旦我讀到隱式行為,我的腳趾就開始轉向 curl。 我同意引用不能是 NULL 是很好的隱式行為。

取消引用 NULL 指針不是問題。 它會使您的應用程序崩潰並且易於調試。 更大的問題是包含無效值的未初始化指針。 這很可能會導致 memory 損壞,從而導致沒有明確來源的未定義行為。

這是我認為引用比指針更安全的地方。 我同意前面的說法,即接口(應該清楚地記錄在案,參見按合同設計,Bertrand Meyer)將參數的結果定義為 function。 現在考慮到這一切,我的偏好 go 盡可能/盡可能地使用參考。

對於指針,您需要它們指向某些東西,因此指針會占用 memory 空間。

例如,采用 integer 指針的 function 將不會采用 integer 變量。 因此,您需要先創建一個指針以傳遞給 function。

作為參考,它不會花費 memory。 您有一個 integer 變量,您可以將其作為參考變量傳遞。 而已。 您不需要專門為它創建引用變量。

暫無
暫無

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

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