標籤:

Google C++ Style Guide 中為什麼禁止使用預設函數參數?

3. 預設參數(Default Arguments)

禁止使用預設函數參數。

優點:經常用到一個函數帶有大量預設值,偶爾會重寫一下這些值,預設參數為很少涉及的例外情況提供了少定義一些函數的方便。

缺點:大家經常會通過查看現有代碼確定如何使用API,預設參數使得複製粘貼以前的代碼難以呈現所有參數,當預設參數不適用於新代碼時可能導致重大問題。

結論:所有參數必須明確指定,強製程序員考慮API和傳入的各參數值,避免使用可能不為程序員所知的預設參數。

以上複製自Google C++ Style Guide。

上面的解釋可能過於理論化了。希望回答者能具體的解釋一下。最好有一兩個例子。

謝謝


主要問題在於:修改默認參數的值是API compatible,但不是ABI compatible!

默認參數雖然默認,不用顯示寫出,但其還是函數介面的一部分,如果你修改了默認值,所有的consumer必須重新編譯 (默認參數值已被編譯進consumer的binary當中),但有的時候,我可能希望能夠保持ABI compatibility:

void optimize(int level=3);

或者

void optimize(int level);
void optimize_maximum() {
optimize(3)
}

用戶可以直接調用optimize(3),自己顯示指定level為3,沒有問題

如果有一個非常常用的case,實現類似於默認參數的簡潔性,我們可以包一個函數optimize_maximum,但此時裡面的參數level不再是介面的一部分,而是實現細節。所以當我把optimize_maximum的實現改成optimize(5)的時候,對用戶應該是透明的。也就是說是ABI compatiable的


虛函數的話,默認實參會坑人,給你舉個例子:

virtual void Base::print(int para=1)

{

std::cout&<&

}

virtual void Derived::print(int para=2)

{

std::cout&<&

}

Base *p = new Derived;

p-&>print();

猜猜結果是多少?


@黃柏炎 說的完全正確,就是 ABI 的問題。我再補充一點:versioning,特別是基礎庫和應用程序由不同的團隊維護的情況下。

借用他的例子,假設你的應用程序的代碼里寫了一句:

optimize();

如果事後(程序發布上線運行幾周之後)你想知道程序指定的 optimize level 是幾,你要麼反彙編去看,要麼必須察看編譯這個應用程序時用的庫的頭文件(那個 revision snapshot),而不能簡單地察看這個庫當前的頭文件,因為這個預設值在這段時間可能已經改過了。

如果 A 庫定義了帶預設參數的 optimize(),B 庫調用了 optimize(),而你的程序使用了 B 庫,那麼要找出你的程序實際使用的 optimize level,看 B 的源代碼是沒用的,看 A 現在的源碼也沒用,你需要察看你的程序用到的那份 B 庫(lib或so)在編譯的那一刻用的 A 的頭文件的源碼。

另外,編譯器會在每個調用端幫你填上(傳入)預設參數,因此對於某些特別常用的函數,提供短參數的 overload 可以縮小 binary 的體積,例如 https://code.google.com/p/google-glog/source/browse/trunk/src/glog/logging.h.in#1172 所言。

最後,帶預設參數的函數的原型(簽名)是含有那個參數類型的,例如:

void (*fp)() = optimize; // 錯誤 error: invalid conversion from 『int (*)(int)』 to 『int (*)()』
void (*fpi)(int) = optimize; // 正確

因此你不能給已有的介面函數添加預設參數,雖然看起來不需要改調用端,但其實可能會 break client code。


函數重載,默認參數,都是初寫組件庫的人常犯的錯誤。

自以為為使用者提供了方便,其實遺患無窮,悔不該當初。


google的coding guideline根本不把C++當C++,C++推薦的所有彌補C坑的用法google都一律禁止掉了,像什麼RAII啊,exception啊,etc。那就是個帶了class的C語言。

如果題主你也想寫帶class的C語言,那你可以學這份。不然的話你還是去看Effective C++和More Effective C++好了。

==================================

具體到這個問題。預設參數說白了不就是少寫幾個重載嘛。如果你不用預設參數,改為少幾個參數的重載然後用「指定的參數」去補充剩下的參數,難道就不會發生它說的那些事情嗎?所以具體到這個問題,實屬無稽之談。

譬如說

int Fuck(Fuckee* fuckee, int count = 100);

和(如果是複雜類型的話,不用預設參數還會有多餘的性能消耗)

int Fuck(Fuckee* fuckee, int count);

int Fuck(Fuckee* fuckee)
{
return Fuck(fuckee, 100);
}

就算你說不許重載,好了第二個Fuck改成Fuck2,其實也根本沒有區別。


Google C++ Style的對應小節跟題主貼的內容有出入:

Default Arguments
link


We do not allow default function parameters, except in limited
situations
as explained below. Simulate them with function
overloading instead, if appropriate.

Pros:
Often you have a function that uses default values, but
occasionally you want to override the defaults. Default
parameters allow an easy way to do this without having to
define many functions for the rare exceptions. Compared to
overloading the function, default arguments have a cleaner
syntax, with less boilerplate and a clearer distinction
between "required" and "optional" arguments.

Cons:
Function pointers are confusing in the presence of default
arguments, since the function signature often doesn"t match
the call signature
. Adding a default argument to an existing
function changes its type, which can cause problems with code
taking its address. Adding function overloads avoids these
problems. In addition, default parameters may result in
bulkier code since they are replicated at every call-site --
as opposed to overloaded functions, where "the default"
appears only in the function definition.

Decision:


While the cons above are not that onerous, they still
outweigh the (small) benefits of default arguments over
function overloading. So except as described below, we
require all arguments to be explicitly specified.


One specific exception is when the function is a static
function (or in an unnamed namespace) in a .cc file
. In
this case, the cons don"t apply since the function"s use is
so localized.


Another specific exception is when default arguments are
used to simulate variable-length argument lists.

標準里把優點說了,跟 @vczh 表達的是一個意思。不過缺點說的就有點鬼扯了:

"Adding a default argument to an existing
function changes its type, which can cause problems with code
taking its address."

default argument只是syntax sugar而已,並不會改變function signature。

=====================================================

評論中的 @布丁 指出我對那句話理解不正確:

確實function signature跟default argument沒有關係。扯到一起說,是因為把N參數函數改成有default
argument的N+1參數版本,看起來好像調用方法沒變,但其實是改了函數的signature,
這就是這裡說的causing
problem了:加個默認參數你得改掉所有這些指針賦值代碼,更坑爹的是你要在call這個函數指針的時候把默認參數值加上,如果哪天你要該默認參數值
那就死得更慘烈,那麼default
argument也就沒有什麼意義。這時候如果你是定義兩個函數兩參數版本調用三函數版本而不是用默認函數,就沒有這些問題。

示例代碼:

// Before change.
void func(int a);
func(42);
void (*func_ptr)(int) = func;

// After change.
void func(int a, int b = 10);
func(42); // Still works.
void (*func_ptr)(int) = func; // Error, wrong function signature.


感覺所有的答案都沒答到點上。排前兩位的答案離題了,說的是另外的缺陷(而且我覺得其實不太重要)。

先說一個例子,某日我代碼審查,發現某人實現了一個基礎類庫中已有的函數。詢問原因。答曰功能有異。但其實是可以設置參數的。但他參考之前的舊代碼都是沒有參數,於是在試用函數後覺得不滿足需求就自己寫了。

例子中由於使用默認參數導致的問題不是太嚴重,也就費些功夫多些冗餘。但如果程序員沒有試用直接抄來就用,是不是就導致問題了?這也就是google說明文字的意思。

幾個答案說提供函數重載更麻煩是對的。但google的意思不是用重載替代默認參數(說實話這就像吸煙不好還是吸毒吧),他的意思很明白:需要幾個參數,就讓調用者明明白白地傳入。

這麼做的優點:可以增強調用者對函數的認識,避免一知半解導致的調用錯誤。

缺點:調用者費事麻煩唄。

我的建議:只為可以預設的參數提供默認值。比如vector的增長倍數,這種不會影響功能正確性的可以考慮使用。但不要濫用默認參數,不要只是因為——哈哈,這是目前的應用場景,為了少打幾個字,我把它們設置為默認值好了。


可能是因為時間的原因,Google C++ Style Guide 中與問題描述有所出入。

Default Arguments

Default arguments are allowed on non-virtual functions when the default is guaranteed to always have the same value. Follow the same restrictions as for function overloading, and prefer overloaded functions if the readability gained with default arguments doesn"t outweigh the downsides below.

Pros:

Often you have a function that uses default values, but occasionally you want to override the defaults. Default parameters allow an easy way to do this without having to define many functions for the rare exceptions. Compared to overloading the function, default arguments have a cleaner syntax, with less boilerplate and a clearer distinction between "required" and "optional" arguments.

Cons:

Defaulted arguments are another way to achieve the semantics of overloaded functions, so all the reasons not to overload functions apply.

The defaults for arguments in a virtual function call are determined by the static type of the target object, and there"s no guarantee that all overrides of a given function declare the same defaults.

Default parameters are re-evaluated at each call site, which can bloat the generated code. Readers may also expect the default"s value to be fixed at the declaration instead of varying at each call.

Function pointers are confusing in the presence of default arguments, since the function signature often doesn"t match the call signature. Adding function overloads avoids these problems.

Decision:

Default arguments are banned on virtual functions, where they don"t work properly, and in cases where the specified default might not evaluate to the same value depending on when it was evaluated. (For example, don"t write void f(int n = counter++);.)

In some other cases, default arguments can improve the readability of their function declarations enough to overcome the downsides above, so they are allowed. When in doubt, use overloads.

總體來說,虛函數中不要用,值不確定的不要用,有奇奇怪怪的副作用的不要用。只有在值很簡單,且不會對可讀性造成干擾時使用。


google的新語言golang就是不存在預設函數參數和函數重載的。


這是一個習慣問題,跟誰是誰非沒啥關係。就像每個公司都可能有自己的編碼習慣一樣,很多條款不是強制的但不乏借鑒意義。

我自己覺得預設函數除了可以少些代碼之外沒啥其他優點。但少這個參數,未來的維護成本可能就會增加很多。開始我是沒注意過這個,還有布爾值做參數、2個參數類型完全一樣等等,但長時間過後你再維護項目代碼,甚至使用原代碼的時候,你也許就會覺得有些習慣即便看上去有些謹小慎微,但還是值得注意的。

當然,如果你能保證自己的文檔寫得相當清晰,可以無視很多謹小慎微的做法,但也同樣會給後期維護和重用帶來附加成本。


舊貼也更新一下:

最新的 Google C++ Style Guide已經允許默認參數了,上面些為這個規則站隊的怎麼看?

Google C++ Style Guide

Default arguments are allowed on non-virtual functions when the default is guaranteed to always have the same value. Follow the same restrictions as for function overloading, and prefer overloaded functions if the readability gained with default arguments doesn"t outweigh the downsides below.

Default arguments are banned on virtual functions, where they don"t work properly, and in cases where the specified default might not evaluate to the same value depending on when it was evaluated. (For example, don"t write void f(int n = counter++);.)

In some other cases, default arguments can improve the readability of their function declarations enough to overcome the downsides above, so they are allowed. When in doubt, use overloads.


推薦閱讀:

C++ 如何自動推導遞歸 lambda 函數的類型?
關於c++ default constructor的問題,這個說法對嗎?
c++中的左值跟右值怎麼區分?
語句str2= str1 + (str1.size() - 1," ")為什麼只有1個空格添加進去了?

TAG:代碼風格 | C | CC |