10

listの標準テンプレート ライブラリのドキュメントには、次のように記載されています。

void push_back ( const T& x );

最後に要素を追加 リストの最後、現在の最後の要素の直後に新しい要素を追加します。この新しい要素の内容は、x のコピーに初期化されます。

これらのセマンティクスは Java のセマンティクスとは大きく異なり、私を混乱させます。私が見逃しているSTLの設計原則はありますか? 「常にデータをコピー」?それは私を怖がらせます。オブジェクトへの参照を追加すると、オブジェクトがコピーされるのはなぜですか? オブジェクトだけが渡されないのはなぜですか?

ここには言語設計の決定が必要ですが、スタック オーバーフローや他のサイトで見つけたコメントのほとんどは、このすべてのオブジェクトのコピーが例外をスローする可能性があるという事実に関連する例外スローの問題に焦点を当てています。コピーせずに参照を処理するだけで、これらの例外の問題はすべてなくなります。非常に混乱。

注意: 私が使用しているこのレガシー コード ベースでは、ブーストはオプションではありません。

4

5 に答える 5

11

STL は常に、保存するように指示したとおりに保存します。list<T>は常にのリストであるTため、すべてが値によって格納されます。ポインターのリストが必要な場合はlist<T*>、Java のセマンティクスに似た を使用します。

これは試してみたくなるかもしれませんlist<T&>が、それは不可能です。C++ での参照は、Java での参照とは異なるセマンティクスを持っています。C++ では、オブジェクトを指すように参照を初期化する必要があります。参照が初期化されると、常にこのオブジェクトを指します。別のオブジェクトを指すようにすることはできません。これにより、C++ で参照のコンテナーを持つことができなくなります。Java 参照は C++ ポインタとより密接に関連しているためlist<T*>、代わりに , を使用する必要があります。

于 2012-10-16T14:12:52.600 に答える
6

これは「値セマンティクス」と呼ばれます。C ++は通常、値をコピーするようにコーディングされています。Javaとは異なり、プリミティブ型は別として、参照をコピーします。それはあなたを怖がらせるかもしれませんが、個人的にはJavaの参照セマンティクスは私をもっと怖がらせます。ただし、C ++では、参照セマンティクスが必要な場合は、ポインター(できればスマートポインター)を使用するかどうかを選択できます。そうすれば、慣れ親しんだJavaに近づくことができます。ただし、C ++ではガベージコレクションがないことを覚えておいてください(そのため、通常はスマートポインターを使用する必要があります)。

于 2012-10-16T14:07:50.057 に答える
4

実際、Java は同じように動作します。説明させてください:

Object obj = new Object();
List<Object> list = new LinkedList<Object>();
list.add(obj);

の型はobj何ですか? への参照Objectです。実際のオブジェクトはヒープのどこかに浮かんでいます。Java でできることは、オブジェクトへの参照を渡すことだけです。オブジェクトへの参照をリストのaddメソッドに渡すと、リストはその参照のコピーをそれ自体に格納します。objリストに保存されているその参照の別のコピーに影響を与えることなく、後で名前付き参照を変更できます。(もちろん、オブジェクト自体を変更すると、どちらの参照でもその変更を確認できます。)

C++ にはさらに多くのオプションがあります。Java をエミュレートできます。

class Object {};
// ...
Object* obj = new Object;
std::list<Object*> list;
list.push_back(obj);

の型はobj何ですか? へのポインタObjectです。push_backリストのメソッドに渡すと、リストはそのポインターのコピーをそれ自体に格納します。これは、Java と同じセマンティクスを持っています。

しかし、効率の観点から考えてみると、C++ ポインター/Java 参照はどれくらい大きいのでしょうか? アーキテクチャに応じて、4 バイトまたは 8 バイト。関心のあるオブジェクトがそのサイズ以下のサイズである場合、なぜそれをヒープに配置してから、そのオブジェクトへのポインターをどこにでも渡す必要があるのでしょうか? オブジェクトを渡すだけです:

class Object {};
// ...
Object obj;
std::list<Object> list;
list.push_back(obj);

さて、obj実物です。それをリストのメソッドに渡します。このメソッドは、そのオブジェクトのコピーpush_backをそれ自体に格納します。これは、ある意味で C++ のイディオムです。ポインターが純粋なオーバーヘッドである小さなオブジェクトにとって意味があるだけでなく、非 GC 言語 (偶発的にリークされる可能性のあるヒープには何もない) での作業も容易になり、オブジェクトの寿命が自然に結び付けられている場合リストに追加する場合 (つまり、リストから削除された場合、意味的には存在しなくなるはずです)、オブジェクト全体をリストに保存することもできます。また、キャッシュの局所性の利点もあります (とにかくで使用する場合)。std::vector


「では、なぜpush_back参照引数を取るのですか?」と疑問に思うかもしれません。それには十分に単純な理由があります。すべてのパラメーターは値で渡されます (これも C++ と Java の両方で)。std::listofがある場合はObject*、問題ありません。ポインターを渡すと、そのポインターのコピーが作成され、関数に渡されpush_backます。次に、その関数内で、そのポインターの別のコピーが作成され、コンテナーに格納されます。

ポインタでいいです。しかし、C++ では、オブジェクトのコピーは任意に複雑になる可能性があります。コピー コンストラクターは何でもできます。状況によっては、オブジェクトを 2 回 (1 回は関数に、もう 1 回はコンテナーに) コピーすると、パフォーマンスの問題が発生する可能性があります。push_backそのため、その引数は const 参照によって取得されます。つまり、元のオブジェクトから直接コンテナーに単一のコピーが作成されます。

于 2012-10-16T14:21:23.853 に答える
4

オブジェクトへの参照を追加しません。オブジェクトを参照渡しします。それは違う。参照によって渡さなかった場合、実際の挿入の前に余分なコピーが作成された可能性があります。

そして、コピーが必要なため、コピーを実行します。そうでない場合は、次のようなコードになります。

std::list<Obj> x;
{
   Obj o;
   x.insert(o);
}

o範囲外になったため、リストに無効なオブジェクトが残ります。Java に似たものが必要な場合は、 の使用を検討してshared_ptrください。これにより、Java で慣れ親しんだ利点 (自動メモリ管理と軽量コピー) が得られます。

于 2012-10-16T14:09:10.507 に答える