web-dev-qa-db-ja.com

c ++マップでの挿入とemplaceとoperator []

私は初めてマップを使用していますが、要素を挿入する方法はたくさんあることに気付きました。 emplace()operator[]、またはinsert()に加えて、value_typeまたはmake_pairを使用するなどのバリアントを使用できます。それらすべてに関する多くの情報と特定のケースに関する質問がありますが、全体像はまだ理解できません。したがって、私の2つの質問は次のとおりです。

  1. それらのそれぞれの利点は何ですか?

  2. エンプレースを標準に追加する必要はありましたか?これなしでは不可能だったことがありますか?

159
German Capuano

マップの特定のケースでは、古いオプションは2つのみでした:operator[]およびinsertinsertの異なるフレーバー)。だから私はそれらの説明を始めます。

operator[]find-or-add演算子です。マップ内で指定されたキーを持つ要素を見つけようとし、存在する場合は保存された値への参照を返します。そうでない場合は、デフォルトの初期化で所定の位置に挿入された新しい要素を作成し、それへの参照を返します。

insert関数(単一要素フレーバー)は、value_typestd::pair<const Key,Value>)を受け取り、キー(firstメンバー)を使用して挿入しようとします。 std::mapは、既存の要素がある場合、重複を許可しないため、何も挿入しません。

2つの違いの最初の違いは、operator[]がデフォルトの初期化されたvalueを構築できる必要があるため、デフォルトの初期化できない値型には使用できないことです。 2つの2つ目の違いは、指定されたキーを持つ要素が既に存在する場合に発生することです。 insert関数はマップの状態を変更しませんが、代わりにイテレーターを要素に返します(および挿入されなかったことを示すfalseを返します)。

// assume m is std::map<int,int> already has an element with key 5 and value 0
m[5] = 10;                      // postcondition: m[5] == 10
m.insert(std::make_pair(5,15)); // m[5] is still 10

insertの場合、引数はvalue_typeのオブジェクトであり、さまざまな方法で作成できます。適切な型で直接構築するか、value_typeを構築できるオブジェクトを渡すことができます。これはstd::make_pairオブジェクトの簡単な作成を可能にするため、std::pairが登場する場所です。

次の呼び出しの最終的な効果はsimilarです。

K t; V u;
std::map<K,V> m;           // std::map<K,V>::value_type is std::pair<const K,V>

m.insert( std::pair<const K,V>(t,u) );      // 1
m.insert( std::map<K,V>::value_type(t,u) ); // 2
m.insert( std::make_pair(t,u) );            // 3

しかし、実際には同じではありません... [1]と[2]は実際には同等です。どちらの場合も、コードは同じタイプ(std::pair<const K,V>)の一時オブジェクトを作成し、それをinsert関数に渡します。 insert関数は、バイナリ検索ツリーに適切なノードを作成し、value_type部分を引数からノードにコピーします。 value_typeを使用する利点は、value_type常にmatchesvalue_typeであるため、std::pair引数のタイプを間違えないことです!

違いは[3]です。関数std::make_pairは、std::pairを作成するテンプレート関数です。署名は次のとおりです。

template <typename T, typename U>
std::pair<T,U> make_pair(T const & t, U const & u );

std::make_pairのテンプレート引数は、一般的な使用法であるため、意図的に提供していません。また、テンプレート引数は呼び出しから推測され、この場合はT==K,U==Vであるため、std::make_pairの呼び出しはstd::pair<K,V>を返します(欠落しているconstに注意してください)。署名には、closeであるvalue_typeが必要ですが、std::make_pairの呼び出しから返される値とは異なります。十分に近いため、正しいタイプの一時ファイルを作成し、コピーを初期化します。次に、それがノードにコピーされ、合計2つのコピーが作成されます。

これは、テンプレート引数を提供することで修正できます。

m.insert( std::make_pair<const K,V>(t,u) );  // 4

しかし、それは、ケース[1]で型を明示的に入力するのと同じように、依然としてエラーを起こしやすいです。

ここまでは、insertを呼び出すさまざまな方法があり、value_typeを外部で作成し、そのオブジェクトをコンテナにコピーする必要があります。または、タイプがデフォルトの構築可能およびassignable(意図的にoperator[]のみにフォーカス)で、1つのオブジェクトとのデフォルトの初期化が必要な場合は、m[k]=vを使用できますcopyそのオブジェクトへの値。

C++ 11では、可​​変個引数テンプレートと完全な転送により、emplacing(インプレースで作成)を使用してコンテナに要素を追加する新しい方法があります。異なるコンテナ内のemplace関数は基本的に同じことを行います:sourceからcopyを取得する代わりに、関数は転送されるパラメータを受け取りますコンテナに保存されているオブジェクトのコンストラクタ。

m.emplace(t,u);               // 5

[5]では、std::pair<const K, V>は作成されずにemplaceに渡されますが、tおよびuオブジェクトへの参照はemplaceに渡され、データ構造内のvalue_typeサブオブジェクトのコンストラクターに転送されます。この場合、nostd::pair<const K,V>のコピーがすべて実行されます。これは、C++ 03の代替手段よりもemplaceの利点です。 insertの場合のように、マップ内の値をオーバーライドしません。


私が考えていなかった興味深い質問は、どのようにemplaceを実際にマップに実装できるかということです。これは一般的なケースでは単純な問題ではありません。

Emplace:右辺値参照を利用して、作成済みの実際のオブジェクトを使用します。これは、大規模なオブジェクトに適したコピーまたは移動コンストラクターが呼び出されないことを意味します! O(log(N))時間。

挿入:標準の左辺値参照および右辺値参照のオーバーロード、挿入する要素のリストのイテレータ、および要素が属する位置に関する「ヒント」があります。 「ヒント」イテレーターを使用すると、挿入にかかる時間が一定の時間になる場合があります。そうでない場合は、O(log(N))時間です。

Operator []:オブジェクトが存在するかどうかを確認し、存在する場合はこのオブジェクトへの参照を変更します。そうでない場合は、提供されたキーと値を使用して2つのオブジェクトでmake_pairを呼び出し、挿入関数と同じ作業を行います。これはO(log(N))時間です。

make_pair:ペアを作る以上のことはしません。

エンプレースを標準に追加するための「必要性」はありませんでした。 c ++ 11では、&&タイプの参照が追加されたと考えています。これにより、移動セマンティクスの必要性がなくなり、特定の種類のメモリ管理の最適化が可能になりました。特に、右辺値参照。オーバーロードされたinsert(value_type &&)演算子はin_placeセマンティクスを利用しないため、効率が大幅に低下します。右辺値参照を処理する機能を提供しますが、オブジェクトの構築にある主要な目的を無視します。

12
ChrisCM

最適化の機会と単純な構文は別として、挿入と配置の重要な違いは、後者がexplicit変換を許可することです。 (これは、マップだけでなく、標準ライブラリ全体に適用されます。)

以下に例を示します。

#include <vector>

struct foo
{
    explicit foo(int);
};

int main()
{
    std::vector<foo> v;

    v.emplace(v.end(), 10);      // Works
    //v.insert(v.end(), 10);     // Error, not explicit
    v.insert(v.end(), foo(10));  // Also works
}

これは確かに非常に具体的な詳細ですが、ユーザー定義の変換のチェーンを扱う場合、これを覚えておく価値があります。

10
Kerrek SB