25

std::map::insertのセマンティクスに少し混乱しています。つまり、文句を言っているわけではありません。標準は標準であり、API はそのままです。まだ、

insert意思

挿入操作は、挿入された各要素について、コンテナ内に同じキー値を持つ別の要素が既に存在するかどうかをチェックします。存在する場合、要素は挿入されず、マップされた値はまったく変更されません。

そして、引数が 1 つのバージョンの場合にのみpair<iterator,bool> insert ( const value_type& x );、(新しい、おそらく異なる) 値をキーに挿入したかどうかさえ教えてくれます。私が理解している限り、キーが既に存在する場合、反復子バージョンは挿入を黙って無視します。

私にとって、これは単に直感に反するものであり、挿入時に値の部分が上書きされ、古い値の部分が破棄されることを期待していました。明らかに、STL の設計者の考えは異なっていました。(歴史的な) 理論的根拠を知っている人、または既存のセマンティクスが (より) 理にかなっている方法を完全に説明できる人はいますか?

例:

次のように、単一キー マップに挿入を実装する基本的な方法がいくつかありますstd::map

  • 挿入、既に存在する場合は置換
  • 挿入、既に存在する場合は無視 (これは std::map の動作です)
  • 挿入、既に存在する場合はエラーをスロー
  • 挿入、既に存在する場合は UB

私は今、なぜ(または)insert_or_ignoreよりも理にかなっているのかを理解しようとしています!insert_or_replaceinsert_or_error


私は自分のTC++PLのコピーを調べました(残念ながら私はドイツ語版しか持っていません)。興味深いことに、Stroustrup は 17.4.1.7 章に書いています ( mapの操作をリストします): (ドイツ語からの大まかな翻訳で申し訳ありません)

insert()(...) 通常、( ... ) の呼び出しの前に、キー(原文のまま!) が新しく挿入されたか、または既に存在していたかは気にしません。

マップの場合、提供された値が挿入された場合、または古い値がマップに残っている場合、かなりの違いが生じるため、これはsetにのみ当てはまり、 map には当てはまらないように私には思えます。(キーは同等であるため、明らかに重要ではありません。)


注: 私は、 Effective STLoperator[]の項目 24とそこで提案されている関数について知っています。のセマンティクスの理論的根拠に興味があるのは、個人的には直観に反すると思うからです。efficientAddOrUpdateinsert

4

5 に答える 5

7

insert メソッドはあなたが探しているものではありません。値がまだ存在しない場合は値を作成し、それ以外の場合はそこにある値を置き換える機能は、状況によっては重要であることに同意しますが、他の場合は、例外を処理したり、値を返したりするのではなく、値がまだ存在しない場合にのみ挿入したい。

あなたが探している方法(BoBTFishが上で示したように)はおそらく[]演算子のようです。次のように使用します。

myMap["key"] = "value";

これはマップを調べてキー「key」を見つけ、対応する値を「value」に置き換えます。キーが存在しない場合は、キーが作成されます。どちらの方法もさまざまな状況で非常に役立ちます。私は必要に応じて両方を使用していることに気付きました。

于 2012-05-14T14:33:53.813 に答える
6

公式の理論的根拠についてはわかりませんが、 との二重性に注意してくださいoperator[]

挿入の 2 つのフレーバーが必要であることは明らかです。

  • 純粋に加算
  • 加法的/破壊的

a をmap配列のスパース表現と見なす場合、 の存在はoperator[]理にかなっています。既存の辞書が存在し、この構文を口述したかどうかはわかりません (おそらく、そうではないのでしょう)。

また、すべてのSTL コンテナーには のオーバーロードがいくつかinsertあります。このインターフェースの類似性により、ジェネリック プログラミングが可能になります。

したがって、API には少なくとも 2 つの候補があります:operator[]insert.

ここで、C++ で次のように読みます。

array[4] = 5;

index のセルの内容4が破壊的に更新されているのは当然です。map::operator[]そのため、この破壊的な更新を可能にするために参照を返す必要があるのは自然なことです。

この時点で、純粋に追加的なバージョンも必要になり、このinsert方法があちこちに転がっています。なぜだめですか ?

もちろんinsert、 と同じセマンティクスを与えてから、 topにメソッドoperator[]を実装することもできます。しかし、これはもっと仕事だったでしょう。insert_or_ignore

したがって、驚くべきことに同意しますが、私の推論にはそれほど欠陥はなく、私たちをここに導く状況の説明になる可能性が高いと思います:)


あなたが提案した代替案について:

  • 挿入、既に存在する場合は UB

幸いなことに、そうではありません。

  • 挿入、既に存在する場合はエラーをスロー

Java (および派生物) だけが例外に夢中です。C++ は、例外が例外的な状況に使用されていた時代に考案されました。

  • 挿入、既に存在する場合は置換
  • 挿入、既に存在する場合は無視 (これは std::map の動作です)

選択はそれらのいずれかであったことに同意します。map2 番目のオプションを選択しても、アイテムが挿入されていないことを警告するため、少なくとも単一アイテム バージョンでは、アイテムが既に存在するという事実を完全に無視するわけではないことに注意してください。

于 2012-05-14T15:20:40.623 に答える
3

私はその決定の元の論理的根拠を知っているとは主張しませんが、それを構成することはそれほど難しくありません. おもう ;-)

「挿入または無視」の現在の振る舞いにより、他の 2 つの実装が非常に簡単になります。十分です!」)。

例(その場で書いているので、間違いがあるかもしれません):

template<typename Map>
void insert_or_update(Map& map, typename Map::value_type const& x)
{
  std::pair<typename Map::iterator, bool> result = map.insert(x);
  if (!result.second)
    result.first->second = x.second; // or throw an exception (consider using
                                     // a different function name, though)
}

そのままで、上記の関数は実際にはそれほど変わらないことに注意してくださいoperator[]-はい、デフォルトの初期化を回避しますが、同時に(私は怠け者なので)あなたの最新の移動セマンティクスを利用できませんdate STL はおそらくすでに for をサポートしていますoperator[]

とにかく、他の挿入動作は、キーがまだマップにない場合にのみエンドセンチネルmapを返すため、他のものを実装するのが面倒になります。map::findの助けを借りて<algorithm>(特にlower_bound)、もちろん、実装の詳細やループなどの醜い一般的な構造を溺れさせることなく、パフォーマンスの高いアクセサリ関数を記述することは依然として可能です ;-)。

于 2012-05-19T16:37:18.767 に答える
0

pair<iterator,bool><- bool 部分は挿入が成功したかどうかを教えてくれませんか?

bool 部分が false の場合は、返されたイテレータの値部分を更新するだけで、既存のアイテムを同じキーで更新できます。

于 2012-05-14T14:33:15.857 に答える
0

insert()コンテナ内の既存のオブジェクトが操作されるとは思わないからです。だからこそ、彼らに触れないでください。

于 2012-05-14T14:25:39.793 に答える