T
型の引数を取る関数があるとします。変更しないので、const参照_const T&
_または値T
で渡すかを選択できます。
_void foo(T t){ ... }
void foo(const T& t){ ... }
_
Const参照を渡す前に、値を渡すよりもT
がどれだけ大きくなるかについての経験則はありますか?たとえば、sizeof(T) == 24
を知っているとします。 const参照または値を使用する必要がありますか?
T
のコピーコンストラクターは簡単だと思います。それ以外の場合、質問への答えは、もちろん、コピーコンストラクターの複雑さに依存します。
私はすでに同様の質問を探して、これに出くわしました:
値またはconst参照によるテンプレートの受け渡しまたは...?
ただし、受け入れられた回答( https://stackoverflow.com/a/4876937/1408611 )には詳細は記載されておらず、単に次のように記載されています。
Tが常に数値型またはコピーが非常に安価な型であると予想される場合は、引数を値で取ることができます。
したがって、それは私の質問を解決するのではなく、言い換えます。「コピーするのが非常に安価」であるためには、型はどれくらい小さくなければならないのでしょうか。
価値のあるパフォーマンスの向上があると疑う理由がある場合は、経験則と測定で切り取ります。引用するアドバイスの目的は、理由もなく大量のデータをコピーしないことですが、すべてを参照することによって最適化を危険にさらすことはありません。 「明らかにコピーするのが安い」と「明らかにコピーするのに費用がかかる」の間に何かがある場合は、どちらのオプションも購入できます。決断を下す必要がある場合は、コインを投げてください。
ファンキーなコピーコンストラクターがなく、sizeof
が小さいタイプは、コピーするのに安価です。呼び出し元のコードと関数自体に大きく依存するため、プラットフォームごとであっても、最適な「小さい」の明確な数値はありません。ただあなたの腸の感覚で行ってください。 1、2、3語は小さいです。知っている10人。 4x4マトリックスは小さくありません。
Const参照の代わりに値を渡すことには、コンパイラーknows値が変更されないという利点があります。 「constint&x」は、値が変更できないことを意味するものではありません。これは、コードが識別子xを使用して変更することを許可されていないことを意味するだけです(コンパイラーが気付くキャストなしで)。このひどいが完全に合法的な例を見てください:
static int someValue;
void g (int i)
{
--someValue;
}
void f (const int& x)
{
for (int i = 0; i < x; ++i)
g (i);
}
int main (void)
{
someValue = 100;
f (someValue);
return 0;
}
関数fの内部では、xは実際には定数ではありません。 g(i)が呼び出されるたびに変化するため、ループは0から49までしか実行されません。そして、コンパイラは通常、このようなひどいコードを書いたかどうかをknowしないので、gが呼び出されたときにx mightが変わると想定する必要があります。その結果、「intx」を使用した場合よりもコードが遅くなることが予想されます。
同じことが、参照によって渡される可能性のある多くのオブジェクトにも明らかに当てはまります。たとえば、const&でオブジェクトを渡し、オブジェクトにintまたはunsigned intのメンバーがある場合、char *、int *、またはunsigned int *を使用した割り当てはそのメンバーを変更しますmight 、コンパイラが別の方法で証明できない限り。値で渡されるため、コンパイラーにとって証明ははるかに簡単です。
私の意見では、次の場合に最も適切な経験則は参照渡しです。
sizeof(T) >= sizeof(T*)
この背後にある考え方は、参照によって取得する場合、最悪の場合コンパイラがポインタを使用してこれを実装する可能性があるということです。
もちろん、これは、コピーコンストラクターと移動セマンティクスの複雑さ、およびオブジェクトのライフサイクルの周りに作成される可能性のあるすべての地獄を考慮していません。
また、マイクロ最適化を気にしない場合は、const参照ですべてを渡すことができます。ほとんどのマシンでは、ポインターは4バイトまたは8バイトであり、それよりも小さいタイプはほとんどなく、その場合でもいくつか(8未満)が失われます。バイトコピー操作と、現代の世界ではボトルネックにならない可能性が最も高いいくつかの間接化:)
私は可能な限り値を渡すことを選択すると思います(つまり、セマンティクスで作業する必要がないことを示している場合実際のオブジェクト)。私はコンパイラが適切な移動とコピーの省略を実行することを信頼します。
コードが意味的に正しい場合は、プロファイルを作成して、不要なコピーを作成していないかどうかを確認します。それに応じてそれらを変更します。
このアプローチは、ソフトウェアの最も重要な部分である正確さに集中するのに役立つと思います。そして、私はコンパイラの邪魔をしません---干渉します。禁止---最適化を実行します(私はそれを打ち負かすことはできません)。
そうは言っても、名目上参照はポインタとして実装されます。したがって、バキュームでは、セマンティクス、コピーエリジオン、移動セマンティクスなどを考慮せずに、ポインタよりもサイズが大きいものをポインタ/参照で渡す方が「効率的」です。
抽象「C++」の抽象「T」の場合、経験則では、意図をより適切に反映する方法を使用します。これは、変更されない引数の場合、ほとんどの場合「値渡し」です。さらに、具体的な実世界のコンパイラーは、そのような抽象的な記述を期待し、ソースでこれをどのように行うかに関係なく、最も効率的な方法でTを渡します。
または、ナイビーコンパイルについて話すとおよび構成、「コピーするのが非常に安い」とは「単一のレジスタにロードできるもの」です。本当にそれより安くなることはありません。
値による対参照による「経験則」を使用する場合は、次のようにします。
ここにいる人たちは、構造体/クラスのサイズが小さく、派手なコピーコンストラクターがない場合はほとんどの場合問題ではないということは正しいです。しかし、それは無知であることの言い訳にはなりません。これが 何が起こるか x64のようないくつかの最新のABIで。そのプラットフォームでは、2つのレジスタで渡されるため、経験則の適切なしきい値は、型がPOD型でsizeof()<= 16の場合に値を渡すことであることがわかります。経験則は適切であり、針を動かさないこのような小さな決定に時間を浪費したり、頭脳を制限したりすることを防ぎます。
ただし、問題になる場合もあります。プロファイラーを使用して、これらのケースの1つがあると判断した場合(そして、それが非常に明白でない限り、以前はそうではありませんでした!)、低レベルの詳細を理解する必要があります-それがどのように問題ではないかについての慰めの礼儀を聞いてはいけません、これ= SOはいっぱいです。覚えておくべきことがいくつかあります: