在STL映射中,使用map :: insert比[]更好吗?

时间:2008-11-28 15:42:52

标签: c++ stl map stdmap

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

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

有人可以告诉我这个理由,还是我只是梦见有一个?

13 个答案:

答案 0 :(得分:236)

写作时

map[key] = value;

无法判断您替换 value key,或创建key 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

答案 1 :(得分:53)

当涉及地图中已存在的密钥时,这两者具有不同的语义。所以他们并没有真正直接比较。

但是operator []版本需要默认构造值,然后分配,所以如果这更昂贵然后复制构造,那么它将更昂贵。有时默认构造没有意义,然后就不可能使用operator []版本。

答案 2 :(得分:34)

std::map需要注意的另一件事:

myMap[nonExistingKey];将在地图中创建一个新条目,并将nonExistingKey初始化为默认值。

当我第一次看到它时,这让我感到很害怕(同时我的头撞了一个非常遗留的小虫)。不会期待它。对我来说,这看起来像一个获取操作,我没想到“副作用”。从地图上获取时更喜欢map.find()

答案 3 :(得分:19)

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

:)

答案 4 :(得分:14)

从异常安全的角度来看,

insert更好。

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

  1. map[key] - 使用默认值创建地图元素。
  2. = value - 将值复制到该元素中。
  3. 第二步可能会发生异常。因此,操作将仅部分完成(新元素已添加到地图中,但该元素未使用value初始化)。操作未完成但系统状态被修改的情况称为带有&#34;副作用的操作&#34;。

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

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

      

    如果要插入单个元素,则异常情况下容器中没有变化(强保证)。

答案 5 :(得分:13)

如果您的应用程序速度很快,我会建议使用[]运算符,因为它会创建原始对象的3个副本,其中2个是临时对象,并且迟早会被销毁。

但是在insert()中,创建了4个原始对象的副本,其中3个是临时对象(不一定是&#34;临时对象&#34;)并被销毁。

这意味着额外的时间: 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;
}

Output when insert() is used Output when [] operator is used

答案 6 :(得分:10)

现在在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;)

谢谢!

答案 7 :(得分:9)

map :: insert()的问题在于,如果地图中已存在该键,它将不会替换值。我见过Java程序员编写的C ++代码,他们期望insert()的行为与Java中的Map.put()相同,其中值被替换。

答案 8 :(得分:2)

需要注意的是,您还可以使用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);
}

答案 9 :(得分:1)

这是一个相当有限的案例,但从我收到的评论来看,我认为值得注意。

我见过过去的人以

的形式使用地图
map< const key, const val> Map;

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

const_cast< T >Map[]=val;

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

我从来没有真正从这些人编写的代码中直接遇到任何麻烦,但我强烈感到直到今天仍然存在风险 - 无论多么小 - 当它们很容易避免时,不应该采取这些风险。

如果您要处理的地图值绝对不能被覆盖,请使用insert。不要仅仅为了可读性而做出例外。

答案 10 :(得分: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 ) ;

}

答案 11 :(得分: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; 
} 

答案 12 :(得分:0)

insert()operator[]之间的区别已经在其他答案中得到了很好的解释。但是,C++11C++17分别引入了std::map的新插入方法:

让我简要介绍一下“新”插入方法:

  • emplace():正确使用此方法时,通过构造要插入的元素可以避免不必要的复制或移动操作。与insert()类似,仅当容器中没有具有相同键的元素时才插入元素。
  • insert_or_assign():此方法是operator[]的“改进”版本。与operator[]不同,insert_or_assign()不需要映射的值类型是默认可构造的。这克服了例如上述的缺点。在Greg Rogers' answer中。
  • try_emplace():此方法是emplace()的“改进”版本。与emplace()不同,try_emplace()不会由于映射中已经存在的键而导致插入失败,因此不会修改其参数(由于移动操作)。

有关insert_or_assign()try_emplace()的更多详细信息,请参见我的回答here

Simple example code on Coliru