前段时间,我和同事讨论了如何在STL映射中插入值。I prefer map[key] = value;因为它让人感觉自然,读起来清晰,而他更喜欢地图。插入(std:: make_pair(关键字,值))。

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

有人能告诉我原因吗,还是我只是在做梦?


当前回答

如果你的应用程序是速度关键,我会建议使用[]操作符,因为它创建了原始对象的总共3个副本,其中2个是临时对象,迟早会被销毁为。

但是在insert()中,创建了原始对象的4个副本,其中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;
}

其他回答

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

我以前见过有人用地图的形式

map< const key, const val> Map;

为了避免意外重写值的情况,然后继续写一些其他的代码:

const_cast< T >Map[]=val;

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

实际上,我从来没有因为这些人写的代码而遇到过任何直接的麻烦,但直到今天,我仍然强烈地感觉到,当风险可以轻松避免时,不应该冒任何风险——无论风险有多小。

在处理绝对不能重写的映射值的情况下,请使用insert。不要仅仅为了可读性而破例。

关于std::map还有一点需要注意:

关联(nonExistingKey);将在map中创建一个新条目,键指向初始化为默认值的nonExistingKey。

当我第一次看到它时(当我的头撞到一个讨厌的遗留bug时),这把我吓坏了。没想到会这样。对我来说,这看起来像一个get操作,我没有预料到“副作用”。当从你的地图获取时,更倾向于map.find()。

从异常安全的角度来看,Insert更好。

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

Map [key] -创建一个默认值的Map元素。 = value—将值复制到该元素中。

第二步可能发生异常。因此,该操作将只完成部分(一个新元素被添加到map中,但该元素没有初始化值)。当一个操作没有完成,但系统状态被修改时,这种情况被称为有“副作用”的操作。

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

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

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

insert()和operator[]之间的区别已经在其他答案中很好地解释了。然而,c++ 11和c++ 17分别引入了新的std::map插入方法:

c++ 11提供了emplace(),在einpoklum的评论和GutiMac的回答中也提到过。 c++ 17提供了insert_or_assign()和try_emplace()。

让我简单总结一下“新”插入方法:

emplace(): When used correctly, this method can avoid unnecessary copy or move operations by constructing the element to be inserted in place. Similar to insert(), an element is only inserted if there is no element with the same key in the container. insert_or_assign(): This method is an "improved" version of operator[]. Unlike operator[], insert_or_assign() doesn't require the map's value type to be default constructible. This overcomes the disadvantage mentioned e.g. in Greg Rogers' answer. try_emplace(): This method is an "improved" version of emplace(). Unlike emplace(), try_emplace() doesn't modify its arguments (due to move operations) if insertion fails due to a key already existing in the map.

有关insert_or_assign()和try_emplace()的更多详细信息,请在这里看到我的回答。

Coliru上的简单示例代码

事实上,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可以稍后使用,例如,在图算法中。在我看来,使用操作符[]的替代选项看起来可读性较差:

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