43

変換コンストラクターの代わりにコピー コンストラクターが呼び出されるのはなぜですか?

初期化には、直接初期化とコピー初期化の 2 つの構文があります。

A a(b);
A a = b;

彼らが異なる定義された行動をとる動機を知りたいです。コピーの初期化には、余分なコピーが含まれており、そのコピーの目的が思い浮かびません。これは一時からのコピーであるため、最適化される可能性があり、おそらく最適化されるため、ユーザーはそれが発生することに依存できません。つまり、余分なコピー自体は、異なる動作の十分な理由にはなりません。なぜ?

4

4 に答える 4

4

憶測にすぎませんが、Bjarne Stroustrup が実際にどうだったかを確認しない限り、より確実にするのは難しいと思います。

このように設計されたのは、プログラマーがそのような動作を予期し、= 記号が使用されたときにコピーが行われ、直接初期化構文では行われないことを期待していたためです。

コピー省略の可能性は、標準の後のバージョンでのみ追加されたと思いますが、よくわかりません-これは、標準の履歴を確認することで誰かが確実に伝えることができるかもしれません.

于 2012-06-27T10:27:09.853 に答える
4

これは temp からのコピーであるため、最適化される可能性があり、おそらく最適化されるでしょう。

ここでのキーワードはおそらく. 標準では、コンパイラがコピーを最適化することは許可されていますが、必須ではありません。一部のコンパイラがこのコードを許可 (最適化) し、他のコンパイラが拒否 (非最適化) した場合、これは非常に矛盾したものになります。

したがって、標準では、これを処理する一貫した方法が規定されています。使用するかどうかに関係なく、誰もがコピー コンストラクターにアクセスできることを確認する必要があります。

アイデアは、すべてのコンパイラがコードを受け入れるか拒否する必要があるということです。そうしないと、移植できなくなります。


別の例を考えてみましょう

A a;
B b;

A a1 = a;
A a2 = b;

s コピー コンストラクターがプライベートの場合、許可することとa2禁止することは、同様に一貫性がありません。a1A


標準テキストから、クラス オブジェクトを初期化する 2 つの方法が異なることを意図していたこともわかります (8.5/16)。

初期化が直接初期化の場合、またはソース型の cv 修飾されていないバージョンが宛先のクラスと同じクラスまたは派生クラスであるコピー初期化の場合、コンストラクターが考慮されます。適用可能なコンストラクターが列挙され (13.3.1.3)、オーバーロード解決 (13.3) によって最適なコンストラクターが選択されます。そのように選択されたコンストラクターは、初期化式または式リストを引数として使用して、オブジェクトを初期化するために呼び出されます。コンストラクターが適用されない場合、またはオーバーロードの解決があいまいな場合、初期化は不適切な形式です。

それ以外の場合 (つまり、残りのコピー初期化の場合)、ソース型から宛先型に、または (変換関数が使用されている場合は) その派生クラスに変換できるユーザー定義の変換シーケンスは、13.3 で説明されているように列挙されます。 1.4 であり、過負荷解決 (13.3) によって最適なものが選択されます。変換ができないか、あいまいな場合は、初期化の形式が正しくありません。選択された関数は、初期化式を引数として呼び出されます。関数がコンストラクターの場合、呼び出しは、目的の型の cv 修飾されていないバージョンの一時を初期化します。一時的なものは prvalue です。呼び出しの結果 (コンストラクターの場合は一時的なもの) は、上記の規則に従って、コピー初期化の宛先であるオブジェクトを直接初期化するために使用されます。場合によっては、中間結果を初期化されるオブジェクトに直接構築することにより、この直接初期化に固有のコピーを排除する実装が許可されます。12.2、12.8を参照。

違いは、直接初期化が構築されたクラスのコンストラクターを直接使用することです。コピー初期化では、他の変換関数が考慮され、これらはコピーする必要がある一時を生成する場合があります。

于 2012-06-27T09:59:14.440 に答える
1

次の例を見てください。

struct X
{
    X(int);
    X(const X&);
};

int foo(X x){/*Do stuff*/ return 1; }
X x(1);
foo(x);

foo私がテストしたコンパイラでは、完全な最適化がオンになっていても、引数は常にコピーされていました。このことから、すべての状況でコピーを排除しない/排除してはならないことがわかります。

ここで、言語設計の観点から考えてみましょう。コピーが必要な場合とそうでない場合のルールを作成する場合に考えなければならないすべてのシナリオを想像してみてください。これは非常に難しいでしょう。また、ルールを思いついたとしても、それは非常に複雑で、人々が理解することはほとんど不可能です. ただし、同時に、どこにでもコピーを強制すると、非常に非効率的になります。これが、ルールが現状のままである理由です。回避できる場合はコピーを作成することを強制せずに、人々が理解できるようにルールをわかりやすくします。

私は今認めなければなりません、この答えはスマの答えと非常に似ています. アイデアは、現在のルールで動作を期待できるということです。それ以外は、人々が従うのが難しすぎるでしょう.

于 2012-06-29T09:40:11.040 に答える
0

次のような組み込み型の初期化:

int i = 2;

歴史的な理由もあり、非常に自然な構文です (高校の数学を思い出してください)。それはより自然です:

int i(2);

たとえ一部の数学者がこの点を主張したとしても。結局のところ、関数 (この場合はコンストラクター) を呼び出して引数を渡すことに不自然なことは何もありません。

組み込み型の場合、これら 2 種類の初期化は同一です。前者の場合、余分なコピーはありません。これが、両方のタイプの初期化を行う理由であり、元々、それらを異なる動作にするという特定の意図はありませんでした。

ただし、ユーザー定義の型があり、言語の宣言された目標の 1 つは、可能な限り組み込み型として動作できるようにすることです。

したがって、コピー構築 (たとえば、何らかの変換関数から入力を取得する) は、最初の構文の自然な実装です。

余分なコピーがある可能性があり、それらが省略される可能性があるという事実は、ユーザー定義型の最適化です。コピーの省略と明示的なコンストラクターの両方が言語に登場したのはずっと後です。標準が一定期間使用した後に最適化を許可することは驚くべきことではありません。また、明示的なコンストラクターをオーバーロード解決の候補から除外できるようになりました。

于 2013-06-17T03:51:46.237 に答える