Scott Meyersの "Effective C++ in Embedded Environment"の例に出くわしました。ここでは、デフォルトのパラメーターを使用する2つの方法が説明されています。
最初のオプションが他のオプションに比べてコストがかかる理由の説明がありません。
void doThat(const std::string& name = "Unnamed"); // Bad
const std::string defaultName = "Unnamed";
void doThat(const std::string& name = defaultName); // Better
最初の例では、一時的なstd::string
がリテラル"Unnamed"
から初期化されます毎回関数は引数なしで呼び出されます。
2番目のケースでは、オブジェクトdefaultName
が初期化されますonce(ソースファイルごと)で、各呼び出しで単純に使用されます。
_void doThat(const std::string& name = "Unnamed"); // Bad
_
これは、_doThat()
が呼び出されるたびに、内容_std::string
_を持つ新しい_"Unnamed"
_が作成されるという点で「悪い」です。
私が使用したすべてのC++コンパイラでの小さな文字列の最適化により、一時的な_"Unnamed"
_内に_std::string
_データが配置されるため、「悪い」とは言いませんbad呼び出しサイトで作成され、ストレージは割り当てられません。したがって、このspecificの場合、一時的な引数にはほとんどコストがかかりません。標準では、小さな文字列の最適化は必要ありませんが、それを許可するように明示的に設計されており、現在使用中のすべての標準ライブラリで実装されています。
文字列が長いと、割り当てが発生します。短い文字列の最適化は短い文字列でのみ機能します。割り当ては高価です。 1つの割り当てが通常の命令よりも1000+倍高いという経験則( multiple microseconds! )を使用すれば、それほど遠くありません。
_const std::string defaultName = "Unnamed";
void doThat(const std::string& name = defaultName); // Better
_
ここでは、内容_"Unnamed"
_を含むグローバルdefaultName
を作成します。これは、静的初期化時に作成されます。ここにはいくつかのリスクがあります。 doThat
が静的な初期化時または破棄時(main
の実行前または実行後)に呼び出された場合、未構築のdefaultName
またはすでに破棄されたもので呼び出すことができます。
一方、ここでは呼び出しごとのメモリ割り当てが発生するリスクはありません。
現在、最新の c ++ 17 の正しいソリューションは次のとおりです。
_void doThat(std::string_view name = "Unnamed"); // Best
_
文字列が長い場合でも割り当てられません。文字列もコピーしません!それに加えて、999/1000の場合、これは古いdoThat
APIのドロップイン置換であり、doThat
にデータを渡し、依存しない場合のパフォーマンスを改善することさえできます。デフォルト引数。
この時点で、組み込みの c ++ 17 サポートは存在しないかもしれませんが、場合によってはすぐにサポートされる可能性があります。また、文字列ビューは、同じことを行う無数の類似したタイプがすでに野生に存在するほど十分に大きなパフォーマンスの向上です。
しかし、レッスンはまだ残っています。デフォルトの引数で高価な操作をしないでください。また、一部のコンテキスト(特に組み込みの世界)では、割り当てに費用がかかる場合があります。
たぶん私は「費用のかかる」と誤解するかもしれません(「正しい」解釈については他の答えを参照してください)が、デフォルトのパラメーターで考慮すべきことの1つは、そのような状況ではうまくスケーリングしないことです:
void foo(int x = 0);
void bar(int x = 0) { foo(x); }
ネストをさらに追加すると、エラーが発生しやすくなります。デフォルト値を複数の場所で繰り返す必要があるためです(つまり、1つの小さな変更でコードの異なる場所を変更する必要があるという意味でコストがかかります)。それを避ける最良の方法は、あなたの例のようです:
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