如何在地图中插入/插入以避免创建临时对象?

时间:2015-01-21 14:33:33

标签: c++11 insert perfect-forwarding emplace

我在阅读C ++ 11书籍之一的建议时,在向容器添加项目时更喜欢emplace而不是insert,以避免创建临时对象(构造函数的调用) /正在插入的对象的析构函数)。但我有点困惑,因为有一些可能性如何将对象添加到地图中,例如。

#include <iostream>
#include <string>
#include <cstdint>
#include <map>

int main()
{
    std::string one     { "one" };
    std::string two     { "two" };

    std::map<uint32_t, std::string> testMap;

    testMap.insert(std::make_pair(1, one));         // 1
    testMap.emplace(2, two);                        // 2
    testMap.insert(std::make_pair(3, "three"));     // 3
    testMap.emplace(4, "four");                     // 4

    using valType = std::map < uint32_t, std::string >::value_type;
    testMap.emplace(valType(5, "five"));            // 5
    testMap.insert(valType(6, "six"));              // 6

    return 0;
}

还有一些引擎盖机制在阅读这样的代码时无法立即显示 - 完美转发,隐式转换......

将项目添加到地图容器的最佳方式是什么?

1 个答案:

答案 0 :(得分:1)

让我们一次考虑一个选项(加上你没有提到的一两个)。

就语义而言,选项1和6基本相同。 usingpair只是拼写map的value_type的两种不同方式。如果您愿意,可以使用typedef而不是using语句添加第三​​种方式:

typedef std::map<uint32_t, std::string>::value_type valType;

...并且拥有相当于#6的C ++ 98/03。所有这三个人最终都做了同样的事情:创建pair类型的临时对象,并将其插入map

版本3和5几乎完全相同。他们使用emplace,但他们传递的内容已经是map的value_type的对象。到emplace本身开始执行时,已经构建了将存储在地图中的对象类型。同样,两者之间的唯一区别在于用于指定pair类型的语法 - 再次,如上所示,使用typedef,您可以使用C ++ 98 / 03相当于当前具有using语句的那个​​。版本3使用insert而版本5使用emplace的事实几乎没有什么区别 - 当调用任一成员函数时,我们已经创建并传递了一个临时对象。

选项2和4实际上都使用emplace更像它可能的意图 - 传递单个组件,将它们完美地转发给构造函数,并在原地构建value_type对象,所以我们避免在任何时候创建任何临时对象。它们之间的主要(唯一?)区别在于我们为value_type的string组件传递的东西是否是字符串文字(需要从中创建临时std::string对象)或者提前创建的std::string对象。

这些之间的选择可能是非平凡的。如果(如上所述)你只做了一次,它根本就没有任何区别 - 无论何时创建,你创建一个字符串对象,然后放入它进入map

因此,为了产生真正的差异,我们需要预先创建字符串对象,然后将相同的字符串对象重复插入map。这本身就很不寻常 - 在大多数情况下,你会做一些事情,比如将外部数据读入字符串,然后将其插入map。如果您确实重复插入(从std::string构造)相同的字符串文字,那么任何合理的编译器都可以检测到结果字符串是循环不变的,并且将string构造提升出来的可能性非常大。循环,给出基本相同的效果。

底线:就map本身的使用而言,选择2和4是等价的。在这两者之间,我不会真正努力使用选项2而不是选项4(即预先创建字符串),但它很可能在大多数时间发生,仅仅因为将单个字符串文字插入到地图中很少有用。您放置在地图中的字符串将更频繁地来自某些外部数据源,因此您将拥有string因为这是(例如)std::getline在您从中读取数据时给出的内容文件。