我正在阅读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 和 6 基本相同。using
和pair
只是拼写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
,您可以拥有当前具有using
语句的C++98/03等效值。版本 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
从文件中读取数据时为您提供的内容。