9

実装1:

foo(const Bar x);

実装2:

foo(const Bar & x);

関数内でオブジェクトが変更されない場合、なぜそれをコピーするのでしょうか(実装1)。

これはコンパイラによって自動的に最適化されますか?

概要:オブジェクトはconst関数宣言のように宣言されていますが、他のエイリアスを介してオブジェクトを編集することは可能です&

あなたがライブラリを書いている人であり、あなたの関数がそれを行わないこと、またはオブジェクトがすべての操作の間接参照コストを正当化するのに十分な大きさであることを知っている場合、それ foo(const Bar & x);は進むべき道です。

パート2

これはコンパイラによって自動的に最適化されますか?

それらが常に同等であるとは限らず、同等の条件が自明ではないことを確立したので、コンパイラがそれらを保証することは一般に非常に困難であり、ほぼ確実に

4

4 に答える 4

5

あなたが尋ねる、

「オブジェクトが関数内で変更されない場合、なぜそれをコピーしますか (実装 1)。」

参照によって渡されたオブジェクトが他のコードによって変更される可能性がある奇妙な状況がいくつかあります。

namespace g { int x = 666; }

void bar( int ) { g::x = 0; }

int foo( int const& a ) { assert( a != 0 );  bar( a );  return 1000/a; }  // Oops

int main() { foo( g::x ); }

1990年代半ば以来、これは私には決して起こりませんでした.

したがって、このエイリアシングは、その型の単一の引数の理論的な問題です。

同じ型の 2 つの引数を使用すると、実際の可能性が高くなります。たとえば、代入演算子には、それが呼び出されたオブジェクトが渡される場合があります。引数が値によって渡される場合 (swap イディオムの最小形式のように) 問題はありませんが、そうでない場合は一般に自己代入を避ける必要があります。

あなたはさらに尋ねます、

「これはコンパイラによって自動的に最適化されますか?」

いいえ、一般的ではありませんが、上記の理由により

一般に、コンパイラは、参照引数のエイリアシングがないことを保証できません (ただし、1 つの例外は、呼び出しのマシン コードがインライン化されている場合です)。

しかし、第三に、言語コンパイラをサポートしていた可能性があります。たとえば、プログラマーにそのような最適化を明示的に受け入れる方法を提供することで、たとえば、「このコードはパスバイを置き換えることで最適化しても安全です」と言う方法があります。値は参照渡しで、どうぞお好きなようにどうぞ、コンパイラー」

于 2012-12-17T17:01:25.633 に答える
5

実際、そのような状況では、通常は方法 2 を使用します。

通常、メソッド 1 はオブジェクトが小さい場合にのみ使用します。そのため、参照を介して繰り返しアクセスするために料金を支払うよりも、オブジェクトを 1 回コピーする方が安価になります (これにもコストがかかります)。TC++PLでは、Stroustrup は複素数クラスを開発し、それを値で渡します。これはまさにこの理由によるものです。

于 2012-12-17T16:47:17.227 に答える
2

状況によっては最適化される可能性がありますが、それを防ぐことができるものはたくさんあります。次の場合、コンパイラはコピーを回避できません。

  • コピー コンストラクタまたはデストラクタには副作用があり、渡された引数は一時的ではありません。
  • のアドレスxまたはそれへの参照を取得し、それを元のアドレスと比較できるコードに渡します。
  • オブジェクトfooは、実行中に変更される可能性があります。たとえば、オブジェクトを変更する他の関数をfoo呼び出すためです。これが「関数内でオブジェクトは変更されない」と言って除外するつもりなのかどうかはわかりませんが、そうでない場合は機能しています。

これらのいずれかがプログラムにとって重要な場合は、それをコピーします。

  • コピーの副作用が必要な場合は、コピーしてください
  • 「あなたの」オブジェクトにユーザー提供の引数とは異なるアドレスを持たせたい場合は、コピーを取ります
  • 関数の実行中にオリジナルに加えられた変更を見たくない場合は、コピーを取ります

コピーがより効率的であると思われる場合は、それをコピーすることもできます。これは、一般に のような「小さい」型の場合であると想定されていますint。標準アルゴリズムの反復子と述語も、値によって取得されます。

最後に、コードがとにかくオブジェクトをコピーすることを計画している場合 (既存のオブジェクトへの代入を含む)、合理的なイディオムは、最初にコピーをパラメーターとして取得することです。次に、パラメータから移動/交換します。

于 2012-12-17T16:57:00.947 に答える
1

オブジェクトが別の場所から変更された場合はどうなりますか?

void f(const SomeType& s);
void g(const SomeType s);

int main() {
    SomeType s;

    std::thread([&](){ /* s is non-const here, and we can modify it */}

    // we get a const reference to the object which we see as const, 
    // but others might not. So they can modify it.
    f(s);

    // we get a const *copy* of the object, 
    // so what anyone else might do to the original doesn't matter 
    g(s);
}

オブジェクトが const であるが、変更可能なメンバーを持っている場合はどうなるでしょうか? その後もオブジェクトを変更できるため、元のオブジェクトのコピーまたは参照があるかどうかは非常に重要です。

オブジェクトに別のオブジェクトへのポインターが含まれている場合はどうなりますか? が const の場合s、ポインターは const になりますが、ポインターが指すものの constness の影響を受けませんs。しかし、コピーを作成すると (うまくいけば) ディープ コピーが得られるので、別の (非 const) オブジェクトを指す別の (const) ポインターを持つ独自の (const) オブジェクトを取得します。

const コピーが const 参照と異なる場合が多数あります。

于 2012-12-17T17:13:59.597 に答える