[英]Are there benefits of passing by pointer over passing by reference in C++?
在 C++ 中通過指針傳遞比通過引用傳遞有什么好處?
最近,我看到許多示例選擇通過指針傳遞 function arguments 而不是通過引用傳遞。 這樣做有好處嗎?
例子:
func(SPRITE *x);
打電話給
func(&mySprite);
對比
func(SPRITE &x);
打電話給
func(mySprite);
nothing
。 這可用於提供可選參數。string s = &str1 + &str2;
使用指針。void f(const T& t); ... f(T(a, b, c));
void f(const T& t); ... f(T(a, b, c));
, 不能像這樣使用指針,因為您不能獲取臨時地址。指針可以接收 NULL 參數,引用參數不能。 如果您有可能想要傳遞“無對象”,請使用指針而不是引用。
此外,通過指針傳遞允許您在調用站點明確查看對象是通過值傳遞還是通過引用傳遞:
// Is mySprite passed by value or by reference? You can't tell
// without looking at the definition of func()
func(mySprite);
// func2 passes "by pointer" - no need to look up function definition
func2(&mySprite);
我喜歡“cplusplus.com”上一篇文章的推理:
當函數不想修改參數且該值易於復制時按值傳遞(ints、doubles、char、bool 等...簡單類型。std::string、std::vector 和所有其他 STL容器不是簡單類型。)
當值復制成本高且函數不想修改指向的值且 NULL 是函數處理的有效預期值時,通過 const 指針傳遞。
當值復制成本高且函數想要修改指向的值時傳遞非常量指針,並且 NULL 是函數處理的有效的預期值。
當值復制成本高且函數不想修改引用的值時,通過 const 引用傳遞,如果使用指針,則 NULL 將不是有效值。
當值復制成本高且函數想要修改引用的值時,通過非連續引用傳遞,如果使用指針,則 NULL 將不是有效值。
在編寫模板函數時,沒有明確的答案,因為有一些權衡考慮超出了本討論的范圍,但可以說大多數模板函數通過值或(const)引用獲取其參數,但是因為迭代器的語法類似於指針的語法(星號表示“取消引用”),任何需要迭代器作為參數的模板函數也將默認接受指針(並且不檢查 NULL,因為 NULL 迭代器概念具有不同的語法)。
我從中得到的是,選擇使用指針或引用參數之間的主要區別是 NULL 是否是可接受的值。 就是這樣。
畢竟,值是否是輸入、輸出、可修改等應該在關於函數的文檔/注釋中。
Allen Holub 的“足夠的繩子可以用腳射擊自己”列出了以下 2 條規則:
120. Reference arguments should always be `const`
121. Never use references as outputs, use pointers
他列出了將引用添加到 C++ 的幾個原因:
const
引用允許您在避免復制的同時具有按值傳遞的語義他的主要觀點是引用不應該用作“輸出”參數,因為在調用站點沒有指示參數是引用還是值參數。 所以他的規則是只使用const
引用作為參數。
就個人而言,我認為這是一個很好的經驗法則,因為它使參數何時是輸出參數變得更加清楚。 然而,雖然我個人總體上同意這一點,但我確實允許自己被團隊中其他人的意見所左右,如果他們認為輸出參數作為參考(一些開發人員非常喜歡它們)。
對之前帖子的澄清:
引用不是獲得非空指針的保證。 (雖然我們經常這樣對待他們。)
雖然可怕的糟糕代碼,就像帶你到木棚壞代碼后面一樣,以下將編譯和運行:(至少在我的編譯器下。)
bool test( int & a)
{
return (&a) == (int *) NULL;
}
int
main()
{
int * i = (int *)NULL;
cout << ( test(*i) ) << endl;
};
我對引用的真正問題在於其他程序員,以下稱為IDIOTS ,他們在構造函數中分配,在析構函數中解除分配,並且無法提供復制構造函數或 operator=()。
突然之間foo(BAR bar)和foo(BAR & bar)之間有了天壤之別。 (自動按位復制操作被調用。析構函數中的釋放被調用兩次。)
值得慶幸的是,現代編譯器會接受相同指針的這種雙重釋放。 15 年前,他們沒有。 (在 gcc/g++ 下,使用setenv MALLOC_CHECK_ 0重新訪問舊方法。)結果,在 DEC UNIX 下,同一內存被分配給兩個不同的對象。 那里有很多調試樂趣......
更實際:
就表達意圖而言,這里的大多數答案都未能解決在函數簽名中包含原始指針的固有歧義。 問題如下:
調用者不知道指針是指向單個對象,還是指向對象“數組”的開頭。
調用者不知道指針是否“擁有”它指向的內存。 IE,該函數是否應該釋放內存。 ( foo(new int)
- 這是內存泄漏嗎?)。
調用者不知道nullptr
是否可以安全地傳遞到函數中。
所有這些問題都通過參考解決:
引用總是指向單個對象。
引用從不擁有它們所引用的內存,它們只是對內存的一個視圖。
引用不能為空。
這使得引用更適合一般用途。 然而,引用並不完美——有幾個主要問題需要考慮。
&
運算符來表明我們確實在傳遞一個指針。 例如, int a = 5; foo(a);
int a = 5; foo(a);
這里根本不清楚 a 是通過引用傳遞的並且可以修改。std::optional<T&>
無效(有充分的理由),指針為我們提供了您想要的可空性。 所以看起來當我們想要一個帶有顯式間接引用的可為空引用時,我們應該達到一個T*
對嗎? 錯誤的!
在我們對可空性的絕望中,我們可能會達到T*
,並簡單地忽略前面列出的所有缺點和語義歧義。 相反,我們應該達到 C++ 最擅長的:抽象。 如果我們簡單地編寫一個環繞指針的類,我們將獲得表達能力,以及可空性和顯式間接性。
template <typename T>
struct optional_ref {
optional_ref() : ptr(nullptr) {}
optional_ref(T* t) : ptr(t) {}
optional_ref(std::nullptr_t) : ptr(nullptr) {}
T& get() const {
return *ptr;
}
explicit operator bool() const {
return bool(ptr);
}
private:
T* ptr;
};
這是我能想到的最簡單的界面,但它可以有效地完成工作。 它允許初始化引用,檢查值是否存在並訪問該值。 我們可以像這樣使用它:
void foo(optional_ref<int> x) {
if (x) {
auto y = x.get();
// use y here
}
}
int x = 5;
foo(&x); // explicit indirection here
foo(nullptr); // nullability
我們已經實現了我們的目標! 現在讓我們看看與原始指針相比的好處。
nullptr
,因為函數作者明確要求提供optional_ref
我們可以從這里開始使接口更復雜,例如添加相等運算符、一元get_or
和map
接口、獲取值或拋出異常的方法、 constexpr
支持。 這可以由你完成。
總之,與其使用原始指針,不如考慮這些指針在您的代碼中的實際含義,並且要么利用標准庫抽象,要么編寫您自己的。 這將顯着改進您的代碼。
並不真地。 在內部,通過引用傳遞本質上是通過傳遞被引用對象的地址來執行的。 因此,通過傳遞指針確實沒有任何效率提升。
然而,通過引用傳遞確實有一個好處。 保證你有一個被傳入的任何對象/類型的實例。如果你傳入一個指針,那么你就有收到一個 NULL 指針的風險。 通過使用傳遞引用,您將隱式 NULL 檢查推高一層給函數的調用者。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.