私は初めてマップを使用していますが、要素を挿入する方法はたくさんあることに気付きました。 emplace()
、operator[]
、またはinsert()
に加えて、value_type
またはmake_pair
を使用するなどのバリアントを使用できます。それらすべてに関する多くの情報と特定のケースに関する質問がありますが、全体像はまだ理解できません。したがって、私の2つの質問は次のとおりです。
それらのそれぞれの利点は何ですか?
エンプレースを標準に追加する必要はありましたか?これなしでは不可能だったことがありますか?
マップの特定のケースでは、古いオプションは2つのみでした:operator[]
およびinsert
(insert
の異なるフレーバー)。だから私はそれらの説明を始めます。
operator[]
はfind-or-add演算子です。マップ内で指定されたキーを持つ要素を見つけようとし、存在する場合は保存された値への参照を返します。そうでない場合は、デフォルトの初期化で所定の位置に挿入された新しい要素を作成し、それへの参照を返します。
insert
関数(単一要素フレーバー)は、value_type
(std::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セマンティクスを利用しないため、効率が大幅に低下します。右辺値参照を処理する機能を提供しますが、オブジェクトの構築にある主要な目的を無視します。
最適化の機会と単純な構文は別として、挿入と配置の重要な違いは、後者が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
}
これは確かに非常に具体的な詳細ですが、ユーザー定義の変換のチェーンを扱う場合、これを覚えておく価値があります。