しばらく前に、STLに値を挿入する方法について同僚と話し合いました maps 。彼はmap.insert(std::make_pair(key, value))
を好んだのに対し、私はmap[key] = value;
を好んだ。
私は彼に尋ねましたが、私たちはどちらも挿入が優れている理由を覚えていませんが、それはスタイルの好みではなく、効率などの技術的な理由があると確信しています。 SGI STLリファレンス は、「厳密に言えば、このメンバー関数は不要です。便宜上のみ存在します。」
誰かがその理由を教えてもらえますか、それともあると夢見ていますか?
書くとき
map[key] = value;
value
のkey
をreplacedしたか、またはcreatedkey
を持つ新しいvalue
。
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
を使用します。
マップにすでに存在するキーに関しては、2つのセマンティクスは異なります。したがって、それらは実際には直接比較できません。
ただし、operator []バージョンでは、デフォルトで値を構築してから割り当てる必要があるため、これがコピー構築よりも高価な場合は、より高価になります。デフォルトの構成が意味をなさない場合があり、operator []バージョンを使用することが不可能になる場合があります。
std::map
で注意すべきもう1つのこと:
myMap[nonExistingKey];
は、デフォルト値に初期化されたnonExistingKey
をキーとする新しいエントリをマップに作成します。
これは私が最初に見たとき(ひどくレガシーなバグに頭を打ちつけている間)、私から地獄を怖がらせました。期待していなかったでしょう。私には、これはget操作のように見えますが、「副作用」は期待していませんでした。マップから取得するときはmap.find()
を優先します。
デフォルトコンストラクターのパフォーマンスヒットが問題にならない場合は、神の愛のために、より読みやすいバージョンを使用してください。
:)
insert
は例外の安全性の点から優れています。
式map[key] = value
は、実際には2つの操作です。
map[key]
-デフォルト値でマップ要素を作成します。= value
-値をその要素にコピーします。2番目のステップで例外が発生する場合があります。その結果、操作は部分的にしか行われません(新しい要素がマップに追加されましたが、その要素はvalue
で初期化されませんでした)。操作が完了していないが、システム状態が変更されている状況は、「副作用」を伴う操作と呼ばれます。
insert
操作は強力な保証を提供し、副作用がないことを意味します( https://en.wikipedia.org/wiki/Exception_safety )。 insert
は完全に実行されるか、マップを変更されていない状態のままにします。
http://www.cplusplus.com/reference/map/map/insert/ :
単一の要素を挿入する場合、例外の場合にコンテナに変更はありません(強力な保証)。
アプリケーションが速度が重要な場合、[]演算子を使用してアドバイスします。元のオブジェクトのコピーが合計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;
}
今c ++ 11では、STLマップにペアを挿入する最良の方法は次のとおりだと思います:
typedef std::map<int, std::string> MyMap;
MyMap map;
auto& result = map.emplace(3,"Hello");
resultは以下とのペアになります:
最初の要素(result.first)。挿入されたペアを指すか、キーが既に存在する場合はこのキーとのペアを指します。
2番目の要素(result.second)。挿入が正しい場合はtrue、何かが間違っている場合はfalse。
PS:注文に関して問題がなければ、std :: unordered_mapを使用できます;)
ありがとう!
Map :: insert()の落とし穴は、キーが既にマップに存在する場合、値を置き換えないことです。私は、Javaプログラマーによって書かれたC++コードを見てきました。そこでは、insert [)は、値が置き換えられるJavaのMap.put()と同じように動作することを期待していました。
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);
}
次に、別の例を示します。operator[]
overwritesキーが存在する場合の値ですが、.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 ) ;
}
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;
}
これはかなり制限されたケースですが、受け取ったコメントから判断すると、注目に値すると思います。
私は過去に人々が
map< const key, const val> Map;
偶発的な値の上書きのケースを回避するために、コードのいくつかの他のビットで書き込みを進めます:
const_cast< T >Map[]=val;
私が覚えているようにこれを行う理由は、これらの特定のコードではマップ値を上書きしないと確信していたためです。したがって、より「読みやすい」メソッド[]
を進めます。
これらの人々によって書かれたコードから実際に直接的なトラブルは一度もありませんでしたが、今日まで、リスクは-どんなに小さくても-簡単に回避できる場合は取るべきではないと強く感じています。
絶対に禁止に上書きされるマップ値を処理する場合は、insert
を使用します。読みやすさのためだけに例外を作らないでください。