插入地图的首选/惯用方式

fre*_*low 91 c++ stl insert stdmap std-pair

我已经确定了四种不同的插入方式std::map:

std::map<int, int> function;

function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));
Run Code Online (Sandbox Code Playgroud)

哪一种是首选/惯用的方式?(还有另一种我没想过的方法吗?)

Geo*_*mer 84

从C++ 11开始,您有两个主要的附加选项.首先,您可以使用insert()列表初始化语法:

function.insert({0, 42});
Run Code Online (Sandbox Code Playgroud)

这在功能上等同于

function.insert(std::map<int, int>::value_type(0, 42));
Run Code Online (Sandbox Code Playgroud)

但更简洁和可读.正如其他答案所指出的,这比其他形式有几个优点:

  • operator[]方法要求映射类型是可分配的,但情况并非总是如此.
  • operator[]方法可以覆盖现有元素,并且无法判断是否发生了这种情况.
  • insert您列出的其他形式涉及隐式类型转换,这可能会降低您的代码速度.

主要缺点是这种形式用于要求密钥和值是可复制的,因此它不适用于例如带有unique_ptr值的映射.这已在标准中修复,但修复程序可能尚未达到标准库实现.

其次,您可以使用以下emplace()方法:

function.emplace(0, 42);
Run Code Online (Sandbox Code Playgroud)

这比任何形式都更简洁insert(),只适用于移动类型unique_ptr,并且理论上可能稍微更有效(尽管一个不错的编译器应该优化差异).唯一的主要缺点是它可能会给你的读者带来一些惊喜,因为emplace方法通常不会那样使用.

  • 还有新的[insert_or_assign](http://en.cppreference.com/w/cpp/container/map/insert_or_assign)和[try_emplace](http://en.cppreference.com/w/cpp/container/地图/ try_emplace) (5认同)
  • 不; 最令人烦恼的解析是当你打算作为变量声明的东西被解析为函数声明时。这不能被解析为函数声明,并且无论如何它也不是变量声明,因此 MVP 不是问题。 (2认同)

ice*_*ime 80

首先,operator[]insert成员函数并不功能等效:

  • operator[]搜索为重点,插入一个默认的构造,如果没有找到值,并返回到您指定的值的参考.显然,如果mapped_type可以从直接初始化而不是默认构造和分配中受益,则这可能是低效的.此方法还使得无法确定是否确实发生了插入,或者您是否仅覆盖了先前插入的键的值
  • insert成员函数不会有任何影响,如果关键是已经存在于地图上,虽然它常常被遗忘,返回std::pair<iterator, bool>它可以是感兴趣的(最值得注意的是,以确定是否插入实际上已完成).

从列出的所有可能性来看insert,这三者几乎相同.提醒一下,让我们看看insert标准中的签名:

typedef pair<const Key, T> value_type;

  /* ... */

pair<iterator, bool> insert(const value_type& x);
Run Code Online (Sandbox Code Playgroud)

那么这三个电话有何不同?

  • std::make_pair依赖于模板参数推导,并且可以(并且在这种情况下)产生value_type与地图的实际不同类型的东西,这将需要额外调用std::pair模板构造函数以便转换为value_type(即:添加constfirst_type)
  • std::pair<int, int>还需要额外调用模板构造函数std::pair才能将参数转换为value_type(即:添加constfirst_type)
  • std::map<int, int>::value_type绝对不容置疑,因为它直接是insert成员函数所期望的参数类型.

最后,我会避免operator[]在插入目标时使用,除非在默认构造和分配中没有额外的成本mapped_type,并且我不关心确定新密钥是否已经有效插入.使用时insert,构建一个value_type可能是要走的路.


In *_*ico 9

第一个版本:

function[0] = 42; // version 1
Run Code Online (Sandbox Code Playgroud)

可能会或可能不会将值42插入到地图中.如果密钥0存在,那么它将为该密钥分配42,覆盖密钥具有的任何值.否则它会插入键/值对.

插入功能:

function.insert(std::map<int, int>::value_type(0, 42));  // version 2
function.insert(std::pair<int, int>(0, 42));             // version 3
function.insert(std::make_pair(0, 42));                  // version 4
Run Code Online (Sandbox Code Playgroud)

另一方面,如果密钥0已经存在于地图中,则不要做任何事情.如果密钥不存在,则插入密钥/值对.

三个插入功能几乎相同.std::map<int, int>::value_typetypedeffor std::pair<const int, int>,并且std::make_pair()显然产生了一个std::pair<>via模板演绎魔术.但是,对于版本2,3和4,最终结果应该相同.

我会用哪一个?我个人更喜欢第1版; 它简洁而"自然".当然,如果不需要覆盖行为,那么我更喜欢版本4,因为它比版本2和3需要更少的输入.我不知道是否有一种事实上的方式将键/值对插入到std::map.

通过其构造函数之一将值插入到地图中的另一种方法:

std::map<int, int> quadratic_func;

quadratic_func[0] = 0;
quadratic_func[1] = 1;
quadratic_func[2] = 4;
quadratic_func[3] = 9;

std::map<int, int> my_func(quadratic_func.begin(), quadratic_func.end());
Run Code Online (Sandbox Code Playgroud)


hon*_*onk 7

由于C++17 std::map提供了两种新的插入方法:insert_or_assign()and try_emplace(),正如sp2danny评论中也提到的。

insert_or_assign()

基本上,insert_or_assign()operator[]. 与 相比operator[]insert_or_assign()不要求地图的值类型是默认可构造的。例如,以下代码无法编译,因为MyClass没有默认构造函数:

class MyClass {
public:
    MyClass(int i) : m_i(i) {};
    int m_i;
};

int main() {
    std::map<int, MyClass> myMap;

    // VS2017: "C2512: 'MyClass::MyClass' : no appropriate default constructor available"
    // Coliru: "error: no matching function for call to 'MyClass::MyClass()"
    myMap[0] = MyClass(1);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

但是,如果您替换myMap[0] = MyClass(1);为以下行,则代码将编译并按预期进行插入:

myMap.insert_or_assign(0, MyClass(1));
Run Code Online (Sandbox Code Playgroud)

此外,类似于insert()insert_or_assign()返回一个pair<iterator, bool>。布尔值是true是否发生插入以及false是否已完成分配。迭代器指向插入或更新的元素。

try_emplace()

与上述类似,try_emplace()是对 的“改进” emplace()。相反emplace()try_emplace()如果由于映射中已存在的键而导致插入失败,则不会修改其参数。例如,以下代码尝试使用已存储在地图中的键放置元素(请参阅 *):

int main() {
    std::map<int, std::unique_ptr<MyClass>> myMap2;
    myMap2.emplace(0, std::make_unique<MyClass>(1));

    auto pMyObj = std::make_unique<MyClass>(2);    
    auto [it, b] = myMap2.emplace(0, std::move(pMyObj));  // *

    if (!b)
        std::cout << "pMyObj was not inserted" << std::endl;

    if (pMyObj == nullptr)
        std::cout << "pMyObj was modified anyway" << std::endl;
    else
        std::cout << "pMyObj.m_i = " << pMyObj->m_i <<  std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出(至少对于 VS2017 和 Coliru):

未插入
pMyObj 无论如何都修改了 pMyObj

如您所见,pMyObj不再指向原始对象。但是,如果替换auto [it, b] = myMap2.emplace(0, std::move(pMyObj));为以下代码,则输出看起来不同,因为pMyObj保持不变:

auto [it, b] = myMap2.try_emplace(0, std::move(pMyObj));
Run Code Online (Sandbox Code Playgroud)

输出:

pMyObj 未插入
pMyObj pMyObj.m_i = 2

Coliru 上的代码

请注意:我试图让我的解释尽可能简短和简单,以适应这个答案。要获得更精确和全面的描述,我建议阅读有关Fluent C++ 的这篇文章


Vik*_*ehr 5

如果你想用键 0 覆盖元素

function[0] = 42;
Run Code Online (Sandbox Code Playgroud)

除此以外:

function.insert(std::make_pair(0, 42));
Run Code Online (Sandbox Code Playgroud)