為什麼要返回一個引用

為了可以返回的大變數可以快速返迴避免複製嗎?我覺得不是函數體自動存儲里的變數是在棧里的,先進後出,一旦程序結束存儲空間釋放,怎麼可能存的下數據,引用也隨之失效,如果你非想這麼做,只能是new一個結構體,那還不如直接返回指針

那如果數據是引用/指針進來的呢,那我可以把它引用出去,而且不用new了,但是你這個引用/指針就是從調用函數哪裡來的,你直接用調用函數訪問不就完事了嗎?

我想知道除了用來快速返回像ifstream這樣我new不出來的變數之外,還有是引用可以快速實現,但是指針不能輕易實現的?


能返回引用自然也就能返回指針,但指針畢竟多一個解指針運算(或者-&>)才能用,而解指針運算複合的多了就鬧心了。

返回引用最常見的是返回類的成員的引用,這些成員可能本身是某些private/protected欄位的一部分,但在符合條件時允許外部直接修改,容器類當中很常見。

普通函數也可以返回static變數的引用。

有的時候內部封裝了一些常用的邏輯,比如兩個參數a,b,如果a.B()為空則返回b,否則返回*a.B(),這種引用雖然從外面來但也很方便啊。

還有的時候返回其中某個參數的引用可以方便鏈式調用,比如C++流操作就是cout&


返回引用一般有這些目的:

(1) 訪問某個參數(以引用傳入,或是 *this )的一部分。這一部分可以是邏輯上的,並不一定是子對象,比如容器的元素。

(2) 訪問某個名字不可在外部訪問的對象。比如單例的對象。

(3) 較少用:返回參數對象本身,但改變其 cv 限定或值類別。 std::move 、 std::forward 、 std::as_const 為此類。

題主的意思可能是 (1) 的目的可以通過訪問對象的數據成員的方式達成。

但一般來說有這樣的問題:

(1) 如果通過可變(非 const )的方式訪問對象,並允許直接其訪問成員對象,則成員對象本身可能肆意修改,導致對象的 invariant (不變數)被破壞。 private/protected 的目的之一就是為了避免意外的修改通過編譯。

(2) 如果對象是容器或類似容器,而且其「元素」存儲於對象之外。則通過不可變( const )方式訪問對象時,由於「元素」是通過指針間接訪問的, const 限定不會傳播給「元素」。這可能是不想要的。

而單例的情況是如果外部可以訪問對象名,就要求對象類型有個可以公開訪問的構造函數,這一般會違背單例的要求。


除了某些特殊情況(例如 std::forward 等類型轉換工具函數)一般的函數(自由函數)確實沒有必要返回引用。

但有些時候我們希望一些類的成員函數返回引用,以便於修改類內部的數據。例如 @Hengrui Zhang 提到的 「std::vector、std::map 等 STL 容器的 operator[]」返回容器內數據的引用以方便外部修改。


一、 有一種函數叫成員函數,它可以返回成員數據的引用

問:為了可以返回的大變數可以快速返迴避免複製嗎?我覺得不是函數體自動存儲里的變數是在棧里的,先進後出,一旦程序結束存儲空間釋放,怎麼可能存的下數據,引用也隨之失效,如果你非想這麼做,只能是new一個結構體,那還不如直接返回指針

class A
{
public:
std::string const GetStr() const
{
return _s; //返回的是引用
}
private:
std::string _s;
};

這種情況下: 沒有你說的函數體內(臨時的)棧變數問題,但就是能讓調用者很爽也很放心,比如,假設調用者只是想看一下_s的值:

cout &

二、很多時候確實是源自C的傳入a指針再返回a指針的老風格

問:那如果數據是引用/指針進來的呢,那我可以把它引用出去,而且不用new了,但是你這個引用/指針就是從調用函數哪裡來的,你直接用調用函數訪問不就完事了嗎?

C語言(標準庫中)常有傳入一個指針對其內容做某種操作,然後再返回該指針的函數 ,比如:

http://www.cplusplus.com/reference/cstring/strcpy/?

www.cplusplus.com

char * strcpy ( char * destination, const char * source );

destination的內容會被填充上source中的內容,然後返回的就是destination。這麼做的考慮主要原因是讓這類函數可以「級聯」操作,次要原因是確實可以在某些時候省去一個步驟,比如說,想將s複製給d1和d2,並輸出d2的話,在那個以代碼短就「政治正確」的年代,常有人就會有個這樣寫:

printf(「%s」, strcpy(d2, strcpy(d1, s)));

我倒是挺贊成你說的:「直接訪問就完事」。在《白話C++》 強調過,不要輕易為了(讓已經熟練的使用者)多加一點點調用方便性,而犧牲直觀性。

因為語言本身就來自C,所以確實有不少C++代碼仍然保留這種風格,只不過由於C++支持引用,所以這個用法也延續到引用身上而已。比如,寫一個函數讓字元串中的每個英文小寫字母變成大寫,延續C風格的寫法是:

std::string ToUpper(std::string s)
{
for (auto c : s)
{
if (c &>= a c &<= z) c -= (a - A); } return s; }

然後假設有人在將某字元串變成大寫後,正巧也要傳遞給另一函數,並且正好他也熟悉這種經(古)典(老)風格的作法,他自然會這麼使用:

extern void next_step(std::string const ); //next_step的原型

std::string s = "aAbBcC";
...
next_step(ToUpper(s));
...

現在有許多C++庫已經不愛這種寫法了。我記得boost庫中字元串演算法中,如果修改的是入參自身,就簡單聲明為返回void,如果是返回一個新的字元串,則在函數名字上加上「Copy」後綴。比如類似前述的操作,boost有兩個函數,去除模板及本地化什麼的後,簡化可得到:

//修改的是入參src自身,告別C風格, 不返回src:
void to_upper(WritableRangeT src);

//不修改入參src,返回的新的變數(不是引用):
SequenceT to_lower_copy(SequenceT const Output);

一票函數全是這樣處理。詳見:https://www.boost.org/doc/libs/1_71_0/doc/html/string_algo/reference.html。

三、 C++中有更多慣用法,需要返回引用入參的引用

如果boost字元串演算法操作算是一種新潮流,但也只是說不應濫用返回被改變的指針入參或引用入參的「慣用法」,而不是說要杜絕。因為在C++中,有更多新的慣用法,需要返回某個入參本身,並且要求返回引用而不是指針。最典型的就是各類流式「級聯」操作的需要啦。比如我們熟悉的這句代碼:

cout &

首先要知道 ,它本質是這樣一行代碼(完全可以通過編譯):

cout.operator &

如果看著眼花,可以將 「operator &

cout.foo("Hello world").foo(endl);

函數「operator &

cout &

強調一下,因為有操作符重載,所以支不支持「級聯」操作並不是簡單的「風格」問題,而是涉及到C++支持操作符重載時的一個原則(見 《C++語言的設計和演化》)。盡量讓重載後操作符行為,和原生操作符行為一致。簡單一句話:小學生都懂 1 + 2 + 3 。所以你重載的 + 操作行為,不管a,b,c是什麼,但最好也支持 a + b + c 。

另:返回this這種做法,已經不是C++的專利了。Java等語言也開始有這種搞法。可見,這種做法應該不算很難理解或很違反人類思維才對。

這種情況下,返回指針是很要命的,比如前面說的 cout 中的 &

(*(cout & operator &

四、 最後,返回引用和返回指針,語義不同

引用當然也可以有「空引用」,C++又不是Rust,所以編譯器不想也不太可能保證有人惡意或無意中造出一個空引用或「野引用」。(想起當年某些使用MFC寫的程序,崩出一個對話框,上面寫著某個CPP文件的某一行的某個變數是空引用……)。題主說的返回一個棧變數的引用,就是一個野引用。特別是跨模塊(dll)時……

但重點是,在語義上,引用是不應該為空的。所以在std::optional (C++17)之前,使用指針表達當前對象可能為空,使用引用表示它不會為空的做法,是相當直觀的。哪怕在2017年之後直至後面很長一段時間,我相信這個慣用法仍然會長期存在而且不應被批評。

如此,關於問題「C++(函數)將返回值(聲明)為引用有什麼用?」的問題,就至少還有一個新答案。函數定義方想以此表明:本操作一定會得到一個實體結果。


主要是STL的vector等容器的operator[],為了同時支持a[x]的取值和a[x]=v的賦值兩種操作,就需要a[x]返回一個左值,用指針的話你得解引用才能成為一個左值表達式,不直觀

至於其他情況很多時候改用指針也不算很不方便,傳聞C++引入引用語法主要就是因為上面這個原因

在不支持引用卻需要支持[]運算符重載的語言中,也有其他實現方式,但會複雜一些,例如python是將[]的取值和賦值分開成兩個重載方法來定義


推薦閱讀:
相关文章