在STL地图中,使用map :: insert比更好吗?

前一段时间,我和一位同事讨论了如何在STL 地图中插入值。 我更喜欢map[key] = value; 因为它感觉自然,而且读起来很清楚,而他更喜欢map.insert(std::make_pair(key, value))

我只是问他,我们都不记得插入效果更好的原因,但我相信这不仅仅是一种风格偏好,而是有效率等技术原因。 SGI STL参考文件只是简单地说:“严格地说,这个成员函数是不必要的:它只是为了方便而存在的。”

有谁能告诉我这个理由,还是我梦想有一个?

当你写

 map[key] = value; 

没有办法告诉你是否replacekeyvalue ,或者是否创build了一个新的key

map::insert()只会创build:

 using std::cout; using std::endl; typedef std::map<int, std::string> MyMap; MyMap map; // ... std::pair<MyMap::iterator, bool> res = map.insert(std::make_pair(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; } 

对于我的大部分应用程序,我通常不关心创build或replace,所以我使用更容易阅读的map[key] = value

当涉及到地图中已经存在的键时,两者有不同的语义。 所以他们不是直接可比的。

但是operator []版本需要默认的构造值,然后赋值,所以如果这样比较昂贵,那么复制构造就会更加昂贵。 有时默认的构造是没有意义的,然后使用operator []版本是不可能的。

另一件要注意的std::map

myMap[nonExistingKey]; 会在map中创build一个新条目,键入nonExistingKey初始化为默认值。

当我第一次看到它的时候,这吓坏了我(当我的头撞到一个遗留的bug时)。 不会预料到的。 对我来说,这看起来像一个get操作,我没有想到的“副作用”。 当从你的地图上获得map.find()时,首选。

如果默认构造函数的性能不是问题,那么为了上帝的爱,请使用更易读的版本。

🙂

如果你的应用程序对速度至关重要,我会使用[]运算符来build议,因为它会创build总共3个原始对象的副本,其中2个是临时对象,迟早会被销毁。

但在insert()中,创build4个原始对象,其中3个是临时对象(不一定是“临时对象”)并被销毁。

这意味着额外的时间:1.一个对象内存分配2.一个额外的构造函数调用3.一个额外的析构函数调用4.一个对象内存释放

如果你的对象很大,构造函数是典型的,析构函数会释放大量的资源,上面的点数甚至更多。 关于可读性,我认为两者都够公平。

同样的问题出现在我的脑海里,但不是可读性,而是速度。 下面是一个示例代码,我通过它来了解我提到的点。

 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()时的输出使用[]运算符时输出

insert从exception安全的angular度来看更好。

expression式map[key] = value实际上是两个操作:

  1. map[key] – 创build一个默认值的地图元素。
  2. = value – 将= value复制到该元素。

第二步可能会发生exception。 结果操作将只部分完成(一个新的元素被添加到映射,但该元素没有初始化value )。 操作未完成但系统状态被修改的情况被称为“副作用”的操作。

insert操作提供了强有力的保证,意味着它没有副作用( https://en.wikipedia.org/wiki/Exception_safety )。 insert要么完全完成,要么以未修改的状态离开地图。

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

如果要插入单个元素,则在exception情况下(强保证),容器中不会有任何更改。

map :: insert()的一个问题是,如果键已经存在于map中,它将不会replace一个值。 我已经看到了Java程序员编写的C ++代码,他们希望insert()的行为与Java中代替值的Map.put()相同。

现在在C ++ 11中,我认为在STL映射中插入一对的最好方法是:

 typedef std::map<int, std::string> MyMap; MyMap map; auto& result = map.emplace(3,"Hello"); 

结果将是一对:

  • 第一个元素(result.first)指向插入的对,或者如果该键已经存在,则使用该键指向该对。

  • 第二个元素(result.second),如果插入是正确的或者是错误的,则返回true。

PS:如果你没有关于你可以使用std :: unordered_map;的情况,

谢谢!

一个注意事项是,你也可以使用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[] 覆盖 key的值(如果存在的话),但是.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 ) ; } 

这是一个相当有限的情况,但从我收到的意见来看,我认为值得注意。

我曾经见过人们用地图的forms

 map< const key, const val> Map; 

以避免意外覆盖的情况,但是继续写下其他一些代码:

 const_cast< T >Map[]=val; 

我记得他们这样做的原因是因为他们确信在这些特定的代码中他们不会覆盖地图值; 因此,继续使用更可读的方法[]

我从来没有从这些人编写的代码中得到任何直接的麻烦,但直到今天,我仍然强烈地感觉到,风险 – 尽pipe很小 – 不应当被容易地避免。

在处理绝对不能被覆盖的映射值的情况下,使用insert 。 不要仅仅为了可读性而做出例外。

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可以在以后使用,例如在graphicsalgorithm中。 在我看来, operator[]替代看起来不太可读:

 string word; map<string, size_t> dict; while(getline(cin, word)) { size_t sz = dict.size(); if (!dict.count(word)) dict[word] = sz; }