出參一定要是指針?
群里之前有同學問過這個問題。
當時的規(guī)范中著拭,強制了這一條,因為彼時的規(guī)范該條目主要來自Google C++ Style Guide其中一個歷史版本牍帚,比較舊了儡遮,當時google還未更新此項。
這個條目是影響面比較大的爭議項暗赶。有不少人反饋鄙币,之前我寫了個issue(https://git.code.oa.com/standards/cpp/issues/71 )提議修改。
經過和委員們的思考和互動討論蹂随,這個條目已經如提議改進了十嘿。
可以看到,現(xiàn)在的規(guī)范(https://git.code.oa.com/standards/cpp#51%E6%8E%A8%E8%8D%90%E8%BE%93%E5%85%A5%E5%92%8C%E8%BE%93%E5%87%BA)
中岳锁, 這個條目已不再強制绩衷。
對我們的影響(僅有積極影響)是:存量代碼中這一條目對應的CodeCC告警(Is this a non-const reference? If so, make const or use a pointer)不用改。
該記錄歸檔于此,以后編程可以注意一下咳燕。
issue引文:
這個強制條目可能需要改進一下
圖中這項是一個影響非常大的爭議項勿决,問題在于強制。
:) 可能條目比較多招盲,大家沒太注意這項低缩。
這個條目的邏輯稍微有點牽強,說了不少引用的優(yōu)點曹货,但是轉而說不讓用引用咆繁,理由是為了可讀性。
再論可讀性
這個條目成立基于這樣一個前提:“相比引用控乾,指針具有更高可讀性”么介。
但是,這個斷言并不成立蜕衡。
所謂優(yōu)勢
猜想到的,可能體現(xiàn)可讀性優(yōu)勢的设拟,是這種場景:
callee:
void copy1(const std::string& a, std::string* b);
void copy2(const std::string& a, std::string& b);
caller:
copy1(foo, &bar);
copy2(foo, bar);
然而慨仿,不少情況調用處是指針變量,不是一個變量實例前加&求址纳胧,這個所謂的優(yōu)勢蕩然無存镰吆。
caller:
ptr_bar = &bar;
copy1(foo, ptr_bar); // 直接傳指針變量
輸入的已經用const修飾了,沒用const修飾的引用很顯然就是輸出參數(shù)跑慕,指針并沒有比引用更顯式地表達輸出万皿。如此,指針作為出參并沒有可讀性優(yōu)勢核行。
即使退一步牢硅,假設這樣的寫法是存在優(yōu)勢的,這樣的場景也有很大的局限性芝雪。
因為會直接'&'這樣寫的减余,只是caller,更確切地說惩系,是初始caller位岔,所以,在一個調用鏈中堡牡,只有在最初的caller中才能體現(xiàn)這個假設的優(yōu)勢抒抬。
語義需要明確,而不是基于隱喻
怎么表達一個參數(shù)晤柄,是類型系統(tǒng)擦剑;用一個參數(shù)具體做什么,是具體的邏輯,這是獨立的兩件事抓于,一件事不該隱喻另一件事做粤。
可能可讀性最好不要通過取址符這一層隱喻來實現(xiàn)。若論可讀性捉撮,自然語言的可讀性最佳怕品,它可以表現(xiàn)在:
- IN/OUT這樣的自然語言增強修飾。(windows以前的用法巾遭。我們可能不用宏肉康,更不會用這么短的宏)。
- 顯式的關鍵字灼舍。(比如RUST的mut吼和,表示mutable,C#的out骑素,表示output)
- 變量名炫乓。(這里對應形參的取名,直接self-documenting為出參)
- 注釋献丑。(如果3仍未滿足)
畢竟末捣,最終caller還是必然得看callee原型的。:) 應該不會有caller不看原型创橄,直接因為有個&箩做,就認為是輸出了吧。
引用比指針具有更高的可讀性
我們的程序有兩種reference妥畏,一種是const的邦邦,一種不是。
引用的語義比指針更加明確醉蚁,在這種非此即彼的場景中更是如此燃辖。
Modern C++中開發(fā)者更愿意把pointer看做nullable reference,而不是把reference看成const-initialized pointer馍管。
引用的本質是常量指針郭赐,用意就是作為變量的別名。這樣用目標變量別名即為出參确沸,語義上也更自然捌锭。
返回值的本質
函數(shù)返回值的本質訴求在于:有一個容器,能容納需要的輸出信息罗捎,caller可以access這個容器观谦。
當你選用函數(shù)出參實現(xiàn)函數(shù)返回值時,這也便成了函數(shù)出參的訴求桨菜。
如果你使用指針豁状,它將是一個不確定的值捉偏,具有不明確的語義。
如果我不希望它為null泻红,那么我為什么總是需要假定它為null呢夭禽?這給開發(fā)者非常高的心智負擔。
若旨在可讀性谊路,那么讹躯,應該直接用Modern C++的返回方式。
強制的影響大
可行性上缠劝,眾多項目底層接口(包括基礎庫的接口)本就是用引用的潮梯,如果強制不能用,這些功能是必然實現(xiàn)不了的惨恭。
成本上秉馏,即使可行,修改成本也過高了脱羡。這是未來成本萝究。
如果強制,可能經過大幅度修改滿足規(guī)范了轻黑,但是未來的一天糊肤,修改了此項。如果要通過檢查氓鄙,可能又得改回來。這是潛在的沉沒成本业舍。
綜上抖拦,未來成本、潛在的沉沒成本很高舷暮。規(guī)范中這一條目改進之后态罪,這些成本可以減少。
該條目的提出者下面,也已經改正了此條目
google minds think alike复颈,谷歌已經改正了這一項。
具體commit:
https://github.com/google/styleguide/commit/7a7a2f510efe7d7fc5ea8fbed549ddb31fac8f3e
https://google.github.io/styleguide/cppguide.html#Inputs_and_Outputs