簡體   English   中英

C ++中的默認參數成本

[英]Cost of Default parameters in C++

我偶然發現了Scott Meyers的“ 嵌入式環境中的有效C ++”中的一個例子,其中描述了兩種使用默認參數的方法:一種被描述為昂貴而另一種被描述為更好的選擇。

我錯過了為什么第一個選項可能比另一個更昂貴的解釋。

void doThat(const std::string& name = "Unnamed"); // Bad

const std::string defaultName = "Unnamed";
void doThat(const std::string& name = defaultName); // Better

在第一個中, 每次在沒有參數的情況下調用函數 ,從文本"Unnamed"初始化臨時std::string

在第二種情況下,對象defaultName初始化一次 (每個源文件),並且只在每次調用時使用。

void doThat(const std::string& name = "Unnamed"); // Bad

這是“壞”的,因為每次調用doThat()doThat()創建一個內容為"Unnamed"的新std::string

我說“壞”並且不錯,因為我使用的每個C ++編譯器中的小字符串優化都會將"Unnamed"數據放在呼叫站點創建的臨時std::string ,而不為它分配任何存儲空間。 因此,在這種特定情況下,臨時參數的成本很低。 該標准不需要小字符串優化,但它明確地設計為允許它,並且當前使用的每個標准庫都實現它。

較長的字符串會導致分配; 小字符串優化僅適用於短字符串。 分配是昂貴的; 如果您使用經驗法則,一次分配比普通指令( 多微秒! )貴1000倍以上,那么您就不會太遠了。

const std::string defaultName = "Unnamed";
void doThat(const std::string& name = defaultName); // Better

這里我們創建一個全局defaultName ,其內容為"Unnamed" 這是在靜態初始化時創建的。 這里有一些風險; 如果在靜態初始化或銷毀時( main運行之前或之后)調用doThat ,則可以使用未defaultName或已經銷毀的defaultName調用它。

另一方面,這里不存在每次調用內存分配的風險。


現在,現代的正確解決方案是:

void doThat(std::string_view name = "Unnamed"); // Best

即使字符串很長也不會分配; 它甚至不會復制字符串! 最重要的是,在999/1000個案例中,這是舊的doThat API的替代品,它甚至可以在您將數據傳遞到doThat而不依賴於默認參數時提高性能。

此時,嵌入式中的支持可能不存在,但在某些情況下可能很快就會出現。 字符串視圖是一個足夠大的性能提升,有許多類似的類型已經在野外做同樣的事情。

但教訓仍然存在; 不要在默認參數中執行昂貴的操作。 在某些情況下(特別是嵌入式世界),分配可能會很昂貴。

也許我誤解了“代價高昂”(對於“正確的”解釋,請參見另一個答案),但是默認參數需要考慮的一件事是它們在這樣的情況下不能很好地擴展:

void foo(int x = 0);
void bar(int x = 0) { foo(x); }

一旦你添加了更多的嵌套,這就變成了一個容易出錯的噩夢,因為默認值必須在幾個地方重復(即,在一個微小的改變需要改變代碼中的不同位置的意義上代價很高)。 避免這種情況的最佳方法就像在您的示例中:

const int foo_default = 0;
void foo(int x = foo_default);
void bar(int x = foo_default) { foo(x); } // no need to repeat the value here

暫無
暫無

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

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