STLマップでは、[]よりもmap :: insertを使用する方が良いですか?


201

しばらく前に、STL マップに値を挿入する方法について同僚と話し合いました。map[key] = value; 自然な感じで読みやすいので好き だったが、彼は好きだった map.insert(std::make_pair(key, value))

私は彼に尋ねたところ、どちらも挿入の方が良い理由を思い出せませんが、スタイルの好みだけではなく、効率などの技術的な理由があったのだと思います。SGIのSTLの参照は、単純に言う、「厳密に言えば、このメンバ関数は不要である:それは便宜上存在します。」

誰かがその理由を教えてもらえますか、それともそこにあることを夢見ていますか?


2
すばらしい回答をありがとう-彼らは本当に役に立ちました。これは、スタックオーバーフローの最高のデモです。Greg Rogersはパフォーマンスの問題について言及し、どの答えが受け入れられるべきかについて引き裂かれました。netjeffは異なる動作についてより明確です。両方にチェックを付けたいと思います。
danio

6
実際、C ++ 11では、おそらく二重構築を回避するmap :: emplaceを使用するのが最も良いでしょう
einpoklum

@einpoklum:実際、スコット・マイヤーズは彼の講演で「効果的なC ++の進化する検索」を別の方法で提案しています。
Thomas Eding、2015年

3
@einpoklum:新しく構築されたメモリを利用する場合です。しかし、マップの標準要件によっては、エンプレースが挿入よりも遅くなる技術的な理由があります。トークはyoutube.com/watch?v=smqT9Io_bKo @〜38-40分のマークのように、youtubeで自由に利用できます。SOリンクについては、stackoverflow.com
questions / 26446352 /…

1
私は実際にマイヤーズが提示したもののいくつかと議論しますが、それはこのコメントスレッドの範囲を超えており、とにかく、以前のコメントを撤回する必要があると思います。
einpoklum 2015年

回答:


240

あなたが書くとき

map[key] = value;

for を置き換えたかどうか、またはで新しいを作成したかどうかを判断する方法はありません。valuekeykeyvalue

map::insert() のみ作成されます:

using std::cout; using std::endl;
typedef std::map<int, std::string> MyMap;
MyMap map;
// ...
std::pair<MyMap::iterator, bool> res = map.insert(MyMap::value_type(key,value));
if ( ! res.second ) {
    cout << "key " <<  key << " already exists "
         << " with value " << (res.first)->second << endl;
} else {
    cout << "created key " << key << " with value " << value << endl;
}

ほとんどのアプリでは、通常、作成したり置き換えたりするかどうかは気にしないため、読みやすい方を使用していますmap[key] = value


16
map :: insertは値を置き換えないことに注意してください。そして、一般的なケースでは、2番目のケースでは(res.first)->secondなく、使用する方が良いと思いvalueます。
ダレ

1
map :: insertが決して置き換えられないことをより明確にするために更新しました。else使用したvalue方がイテレータよりも明確だと思うので、私は去りました。値のタイプに異常なコピーctorまたはop ==がある場合のみ、それは異なり、そのタイプはマップなどのSTLコンテナーを使用して他の問題を引き起こします。
netjeff 2008年

1
map.insert(std::make_pair(key,value))する必要がありますmap.insert(MyMap::value_type(key,value))。返される型はmake_pairで撮影された型と一致しないinsertと、現在のソリューションは、変換が必要です
dribeas -デビッド・ロドリゲス

1
挿入したのか、単にで割り当てたのかを判断する方法がありますoperator[]。サイズを前後で比較するだけです。Imhoがmap::operator[]デフォルトの構成可能な型のみを呼び出すことができることは、はるかに重要です。
idclev 463035818

@DavidRodríguez-dribeas:良い提案です。私の回答のコードを更新しました。
netjeff

53

マップに既に存在するキーに関しては、2つは異なるセマンティクスを持っています。したがって、それらは実際には直接比較することはできません。

しかし、operator []バージョンでは、デフォルトで値を構築してから割り当てる必要があるため、これがコピー構築よりもコストがかかる場合は、よりコストがかかります。デフォルトの構築が意味をなさない場合があり、その場合、operator []バージョンを使用することが不可能になります。


1
make_pairには、コピーコンストラクターが必要な場合があります。これは、デフォルトのコンストラクターよりも劣ります。とにかく+1。

1
主なことは、あなたが言ったように、それらは異なるセマンティクスを持っているということです。したがって、どちらも他より優れていません。必要なことを行うものを使用してください。
2008年

コピーコンストラクターが、既定のコンストラクターに続いて割り当てよりも悪いのはなぜですか?もしそうなら、クラスを書いた人は何かを逃しました。なぜなら、operator =が何をするにせよ、彼らはコピーコンストラクタで同じことをしたはずです。
スティーブジェソップ

1
デフォルトの構築は、割り当て自体と同じくらい高価な場合があります。当然、割り当てとコピー構築は同等になります。
グレッグロジャース

@Arkadiy最適化されたビルドでは、コンパイラーは多くの不要なコピーコンストラクター呼び出しを削除します。
2015

35

注意すべきもう一つのことstd::map

myMap[nonExistingKey];nonExistingKeyデフォルト値に初期化されるようにキー設定された新しいエントリをマップに作成します。

これは私が初めてそれを目にしたとき、私は地獄を怖がらせました(一方で、あまりにも古いレガシーバグに頭をぶつけていました)。それを期待していなかっただろう。私には、これはget操作のように見え、「副作用」は予期していませんでした。好むmap.find()あなたのマップから取得するとき。


3
ハッシュマップはこのフォーマットではかなり普遍的ですが、それはまともな見方です。彼らが同じ慣習をどれだけ広く使用しているかという理由だけで、それらは「誰も奇妙だとは思わない奇妙なもの」の1つかもしれません
スティーブンJ

19

デフォルトのコンストラクターのパフォーマンスへの影響が問題ではない場合は、神の愛のために、より読みやすいバージョンを使用してください。

:)


5
第二!お奨めこれをマークアップ。あまりにも多くの人々が、鈍さをナノ秒のスピードアップと引き換えにしています。そのような残虐行為を維持しなければならない貧しい魂に私たちを憐れんでください!
Mr.Ree、2008年

6
Greg Rogersは次のように書いています。
ダレ

これは古い質問と回答です。しかし、「より読みやすいバージョン」は愚かな理由です。どちらが最も読みやすいかは、人によって異なります。
バレンティン2016年

14

insert 例外的な安全性の点で優れています。

map[key] = valueは実際には2つの演算です。

  1. map[key] -デフォルト値でマップ要素を作成します。
  2. = value -その要素に値をコピーします。

2番目のステップで例外が発生する可能性があります。その結果、操作は部分的にしか行われません(新しい要素がマップに追加されましたが、その要素はで初期化されていませんでしたvalue)。操作が完了していないが、システム状態が変更されている状況は、「副作用のある操作」と呼ばれます。

insert操作は強力な保証を提供します。つまり、副作用がないことを意味しますhttps://en.wikipedia.org/wiki/Exception_safety)。insert完全に完了するか、マップを変更されていない状態のままにします。

http://www.cplusplus.com/reference/map/map/insert/

単一の要素を挿入する場合、例外が発生してもコンテナに変更はありません(強力な保証)。


1
さらに重要なこととして、挿入ではデフォルトの構成可能である必要はありません。
UmNyobe

13

アプリケーションが速度重視の場合、[]演算子を使用してアドバイスします。元のオブジェクトの合計3つのコピーが作成され、そのうち2つは一時オブジェクトであり、遅かれ早かれ破棄されます。

しかし、insert()では、元のオブジェクトの4つのコピーが作成され、そのうち3つは一時オブジェクト(必ずしも「一時」ではない)であり、破棄されます。

つまり、1つのオブジェクトのメモリ割り当て2. 1つの追加のコンストラクタ呼び出し3. 1つの追加のデストラクタ呼び出し4. 1つのオブジェクトのメモリ割り当て解除

オブジェクトが大きい場合は、コンストラクターが一般的です。デストラクターは多くのリソースを解放し、上記のポイントはさらに重要になります。読みやすさに関しては、どちらも十分公平だと思います。

同じ質問が私の頭に浮かびましたが、読みやすさではなく速度についてです。これが私が言及したポイントについて知るために使用したサンプルコードです。

class Sample
{
    static int _noOfObjects;

    int _objectNo;
public:
    Sample() :
        _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside default constructor of object "<<_objectNo<<std::endl;
    }

    Sample( const Sample& sample) :
    _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside copy constructor of object "<<_objectNo<<std::endl;
    }

    ~Sample()
    {
        std::cout<<"Destroying object "<<_objectNo<<std::endl;
    }
};
int Sample::_noOfObjects = 0;


int main(int argc, char* argv[])
{
    Sample sample;
    std::map<int,Sample> map;

    map.insert( std::make_pair<int,Sample>( 1, sample) );
    //map[1] = sample;
    return 0;
}

insert()が使用されたときに出力されます []演算子を使用したときに出力されます


4
次に、完全な最適化を有効にして、そのテストを再度実行します。
2015

2
また、演算子[]が実際に行うことを考慮してください。最初にマップを検索して、指定されたキーに一致するエントリを探します。見つかった場合は、そのエントリの値を指定された値で上書きします。そうでない場合は、指定されたキーと値を使用して新しいエントリを挿入します。マップが大きくなるほど、演算子[]によるマップの検索に時間がかかります。ある時点で、これは余分なcopy c'tor呼び出しを補う以上のものになります(コンパイラーが最適化マジックを実行した後でも、それが最終プログラムに残っている場合でも)。
2015

1
@antred insertは同じ検索を実行する必要があるため、検索結果に違いはありません[](マップキーが一意であるため)。
Sz。

プリントアウトで何が起こっているかを示す良い例ですが、これらの2ビットのコードは実際には何をしているのでしょうか。元のオブジェクトの3つのコピーがまったく必要なのはなぜですか。
rbennett485 2016

1.代入演算子を実装する必要があります。そうしないと、デストラクタで誤った数値が取得されます。 :move(サンプル))); "
Fl0 2016

10

現在、c ++ 11では、STLマップにペアを挿入する最善の方法は次のとおりです。

typedef std::map<int, std::string> MyMap;
MyMap map;

auto& result = map.emplace(3,"Hello");

結果はとペアになります。

  • 最初の要素(result.first)。挿入されたペアを指すか、キーが既に存在する場合はこのキーとのペアを指します。

  • 2番目の要素(result.second)。挿入が正しい場合はtrue、挿入が正しくない場合はfalse。

PS:順序について大文字と小文字を区別しない場合は、std :: unordered_map;)を使用できます。

ありがとう!


9

map :: insert()の問題点は、キーが既にマップに存在する場合、値を置き換えないことです。Javaプログラマーがinsert()が値が置き換えられるJavaのMap.put()と同じように動作することを期待して書かれたC ++コードを見てきました。


2

1つの注意点は、Boost.Assignも使用できることです

using namespace std;
using namespace boost::assign; // bring 'map_list_of()' into scope

void something()
{
    map<int,int> my_map = map_list_of(1,2)(2,3)(3,4)(4,5)(5,6);
}

1

次に、別の例を示しoperator[] ます。存在する場合はキーの値を上書きしますが、存在する場合は値を.insert 上書きしません

void mapTest()
{
  map<int,float> m;


  for( int i = 0 ; i  <=  2 ; i++ )
  {
    pair<map<int,float>::iterator,bool> result = m.insert( make_pair( 5, (float)i ) ) ;

    if( result.second )
      printf( "%d=>value %f successfully inserted as brand new value\n", result.first->first, result.first->second ) ;
    else
      printf( "! The map already contained %d=>value %f, nothing changed\n", result.first->first, result.first->second ) ;
  }

  puts( "All map values:" ) ;
  for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
    printf( "%d=>%f\n", iter->first, iter->second ) ;

  /// now watch this.. 
  m[5]=900.f ; //using operator[] OVERWRITES map values
  puts( "All map values:" ) ;
  for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
    printf( "%d=>%f\n", iter->first, iter->second ) ;

}

1

これはかなり制限されたケースですが、受け取ったコメントから判断すると、注目に値すると思います。

過去の人々が地図を

map< const key, const val> Map;

偶発的な値の上書きのケースを回避し、その後、他のいくつかのコードを書きます。

const_cast< T >Map[]=val;

私が覚えているようにこれを行う理由は、これらの特定のコードのビットでマップ値を上書きしないことを確信していたためです。そのため、より「読みやすい」メソッドを使用し[]ます。

私は実際にこれらの人々によって書かれたコードから直接的な問題を経験したことはありませんが、今日まで、リスクは-どんなに小さくても-簡単に回避できる場合は取るべきではないと強く感じています。

絶対に上書きしてはならないマップ値を処理する場合は、を使用しますinsert。読みやすさのためだけに例外を作らないでください。


複数の異なる人々がそれを書いたのですか?以前の値は非常に非constであるため上書きされるため、確かにinsert(ではなくinput)を使用してくださいconst_cast。または、値のタイプをとしてマークしないでくださいconst。(この種のものは通常、の最終的な結果でconst_castあるため、ほとんどの場合、どこか他の場所でエラーを示す赤い旗です。)
Potatoswatter

@Potatoswatterその通りです。const_cast []がconst map値とともに使用されているのを目にしているのは、特定のコードのビットで古い値を置き換えないことが確かな場合です。[]自体がより読みやすいため。回答の最後の部分で述べたように、insert値が上書きされないようにする場合に使用することをお勧めします。(ジャストは変更inputinsert-感謝)
dk123

@Potatoswatter私が正しく思い出すと、人々が使用していると思われる主な理由const_cast<T>(map[key])は、1。[]がより読みやすく、2。特定のコードに自信を持っており、値を上書きしない、3。しない知らないコードの他のビットがそれらの値を上書きすることを望んでいます-それゆえconst value
dk123 2012

2
これが行われたことを聞いたことがありません。どこで見ましたか?書くことconst_castはの余分な「読みやすさ」を否定する以上のものであるように思われ[]、その種の自信は開発者を解雇するのに十分な根拠です。トリッキーなランタイム条件は、直感ではなく、防弾設計によって解決されます。
Potatoswatter

@Potatoswatter教育用ゲームを開発していた過去の仕事の1つだったことを覚えています。私は、人々がコードを書いて自分の習慣を変えることはできませんでした。あなたは絶対的に正しいと私は強くあなたに同意します。あなたのコメントから、これはおそらく元の回答よりも注目に値するものであると判断したので、これを反映するように更新しました。ありがとう!
dk123 2012

1

std :: map insert()関数はキーに関連付けられた値を上書きしないため、次のようなオブジェクト列挙コードを記述できます。

string word;
map<string, size_t> dict;
while(getline(cin, word)) {
    dict.insert(make_pair(word, dict.size()));
}

異なる非一意のオブジェクトを0〜Nの範囲のIDにマップする必要がある場合、これはかなり一般的な問題です。これらのIDは、たとえばグラフアルゴリズムなどで後で使用できます。operator[]私の意見では、代わりの選択肢は読みにくくなります:

string word;
map<string, size_t> dict;
while(getline(cin, word)) {
    size_t sz = dict.size();
    if (!dict.count(word))
        dict[word] = sz; 
} 
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.