插入地图的首选/惯用方式是什么?

发布于 2024-10-04 18:22:10 字数 307 浏览 10 评论 0 原文

我已经确定了将元素插入 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));

哪一种是首选/惯用方法? (还有其他我没有想到的方法吗?)

I have identified four different ways of inserting elements into a 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));

Which of those is the preferred/idiomatic way? (And is there another way I have not thought of?)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

荒路情人 2024-10-11 18:22:10

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

function.insert({0, 42});

这在功能上等效,

function.insert(std::map<int, int>::value_type(0, 42));

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

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

主要缺点是这种形式过去要求键和值是可复制的,因此它不适用于具有 unique_ptr 值的映射。该问题已在标准中修复,但修复可能尚未达到您的标准库实现。

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

function.emplace(0, 42);

这比 insert() 的任何形式都更简洁,适用于仅移动类型,例如 unique_ptr,理论上可能会稍微更高效(尽管一个像样的编译器应该优化掉差异)。唯一的主要缺点是,它可能会让您的读者感到有点惊讶,因为 emplace 方法通常不以这种方式使用。

As of C++11, you have two major additional options. First, you can use insert() with list initialization syntax:

function.insert({0, 42});

This is functionally equivalent to

function.insert(std::map<int, int>::value_type(0, 42));

but much more concise and readable. As other answers have noted, this has several advantages over the other forms:

  • The operator[] approach requires the mapped type to be assignable, which isn't always the case.
  • The operator[] approach can overwrite existing elements, and gives you no way to tell whether this has happened.
  • The other forms of insert that you list involve an implicit type conversion, which may slow your code down.

The major drawback is that this form used to require the key and value to be copyable, so it wouldn't work with e.g. a map with unique_ptr values. That has been fixed in the standard, but the fix may not have reached your standard library implementation yet.

Second, you can use the emplace() method:

function.emplace(0, 42);

This is more concise than any of the forms of insert(), works fine with move-only types like unique_ptr, and theoretically may be slightly more efficient (although a decent compiler should optimize away the difference). The only major drawback is that it may surprise your readers a little, since emplace methods aren't usually used that way.

冷︶言冷语的世界 2024-10-11 18:22:10

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

  • operator[]搜索对于键,如果未找到,则插入一个默认构造值,并返回您分配值的引用。显然,如果mapped_type可以受益于直接初始化而不是默认构造和分配,那么这可能是低效的。此方法还使得无法确定是否确实发生了插入,或者您是否仅覆盖了先前插入的键的值。
  • 如果该键已存在于映射,虽然经常被忘记,但返回一个可能令人感兴趣的 std::pair (最值得注意的是确定插入是否实际上已完成)。

从所有列出的调用 insert 的可能性来看,所有三个都几乎等效。作为提醒,让我们看一下标准中的 insert 签名:

typedef pair<const Key, T> value_type;

  /* ... */

pair<iterator, bool> insert(const value_type& x);

那么这三个调用有何不同?

  • std::make_pair 依赖于模板参数推导,并且可能(在本例中将会)生成与实际 value_type 不同类型的内容地图,这将需要额外调用 std::pair 模板构造函数才能转换为 value_type (即:将 const 添加到 < code>first_type)
  • std::pair 还需要额外调用 std::pair 的模板构造函数才能转换value_type 的参数(即:将 const 添加到 first_type
  • std::map::value_type 绝对没有任何疑问,因为它直接是 insert 成员函数所期望的参数类型。

最后,当目标是插入时,我会避免使用 operator[] ,除非默认构造和分配 mapped_type 没有额外的成本,并且我不关心确定新密钥是否已有效插入。使用 insert 时,构造 value_type 可能是正确的方法。

First of all, operator[] and insert member functions are not functionally equivalent :

  • The operator[] will search for the key, insert a default constructed value if not found, and return a reference to which you assign a value. Obviously, this can be inefficient if the mapped_type can benefit from being directly initialized instead of default constructed and assigned. This method also makes it impossible to determine if an insertion has indeed taken place or if you have only overwritten the value for an previously inserted key
  • The insert member function will have no effect if the key is already present in the map and, although it is often forgotten, returns an std::pair<iterator, bool> which can be of interest (most notably to determine if insertion has actually been done).

From all the listed possibilities to call insert, all three are almost equivalent. As a reminder, let's have look at insert signature in the standard :

typedef pair<const Key, T> value_type;

  /* ... */

pair<iterator, bool> insert(const value_type& x);

So how are the three calls different ?

  • std::make_pair relies on template argument deduction and could (and in this case will) produce something of a different type than the actual value_type of the map, which will require an additional call to std::pair template constructor in order to convert to value_type (ie : adding const to first_type)
  • std::pair<int, int> will also require an additional call to the template constructor of std::pair in order to convert the parameter to value_type (ie : adding const to first_type)
  • std::map<int, int>::value_type leaves absolutely no place for doubt as it is directly the parameter type expected by the insert member function.

In the end, I would avoid using operator[] when the objective is to insert, unless there is no additional cost in default-constructing and assigning the mapped_type, and that I don't care about determining if a new key has effectively inserted. When using insert, constructing a value_type is probably the way to go.

揪着可爱 2024-10-11 18:22:10

C++17 std::map 提供两种新的插入方法:insert_or_assign()try_emplace(),正如 sp2danny 的评论

insert_or_assign()

基本上,insert_or_assign()操作符[]。与 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;
}

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

myMap.insert_or_assign(0, MyClass(1));

此外,类似于 insert()insert_or_assign() 返回一个pair。如果发生插入,则布尔值为 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;
}

输出(至少对于 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));

输出:

pMyObj 未插入
pMyObj pMyObj.m_i = 2

Coliru 上的代码

请注意:我试图将我的解释保留为尽可能简短,使它们适合这个答案。要获得更准确和全面的描述,我建议阅读 这篇文章介绍Fluent C++

Since C++17 std::map offers two new insertion methods: insert_or_assign() and try_emplace(), as also mentioned in the comment by sp2danny.

insert_or_assign()

Basically, insert_or_assign() is an "improved" version of operator[]. In contrast to operator[], insert_or_assign() doesn't require the map's value type to be default constructible. For example, the following code doesn't compile, because MyClass does not have a default constructor:

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;
}

However, if you replace myMap[0] = MyClass(1); by the following line, then the code compiles and the insertion takes place as intended:

myMap.insert_or_assign(0, MyClass(1));

Moreover, similar to insert(), insert_or_assign() returns a pair<iterator, bool>. The Boolean value is true if an insertion occurred and false if an assignment was done. The iterator points to the element that was inserted or updated.

try_emplace()

Similar to the above, try_emplace() is an "improvement" of emplace(). In contrast to emplace(), try_emplace() doesn't modify its arguments if insertion fails due to a key already existing in the map. For example, the following code attempts to emplace an element with a key that is already stored in the map (see *):

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;
}

Output (at least for VS2017 and Coliru):

pMyObj was not inserted
pMyObj was modified anyway

As you can see, pMyObj no longer points to the original object. However, if you replace auto [it, b] = myMap2.emplace(0, std::move(pMyObj)); by the the following code, then the output looks different, because pMyObj remains unchanged:

auto [it, b] = myMap2.try_emplace(0, std::move(pMyObj));

Output:

pMyObj was not inserted
pMyObj pMyObj.m_i = 2

Code on Coliru

Please note: I tried to keep my explanations as short and simple as possible to fit them into this answer. For a more precise and comprehensive description, I recommend reading this article on Fluent C++.

ら栖息 2024-10-11 18:22:10

第一个版本:

function[0] = 42; // version 1

可能会也可能不会将值 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

另一方面,如果地图中已存在键 0,则不执行任何操作。如果键不存在,则会插入键/值对。

三个插入函数几乎相同。 std::map::value_typestd::pairtypedef,并且 < code>std::make_pair() 显然通过模板推导魔法产生了一个 std::pair<> 。然而,版本 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());

The first version:

function[0] = 42; // version 1

may or may not insert the value 42 into the map. If the key 0 exists, then it will assign 42 to that key, overwriting whatever value that key had. Otherwise it inserts the key/value pair.

The insert functions:

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

on the other hand, don't do anything if the key 0 already exists in the map. If the key doesn't exist, it inserts the key/value pair.

The three insert functions are almost identical. std::map<int, int>::value_type is the typedef for std::pair<const int, int>, and std::make_pair() obviously produces a std::pair<> via template deduction magic. The end result, however, should be the same for versions 2, 3, and 4.

Which one would I use? I personally prefer version 1; it's concise and "natural". Of course, if its overwriting behavior is not desired, then I would prefer version 4, since it requires less typing than versions 2 and 3. I don't know if there is a single de facto way of inserting key/value pairs into a std::map.

Another way to insert values into a map via one of its constructors:

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());
源来凯始玺欢你 2024-10-11 18:22:10

如果要覆盖键为0的元素

function[0] = 42;

否则:

function.insert(std::make_pair(0, 42));

If you want to overwrite the element with key 0

function[0] = 42;

Otherwise:

function.insert(std::make_pair(0, 42));
掩于岁月 2024-10-11 18:22:10

我一直在上述版本之间进行一些时间比较:

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

事实证明,插入版本之间的时间差异很小。

#include <map>
#include <vector>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;
class Widget {
public:
    Widget() {
        m_vec.resize(100);
        for(unsigned long it = 0; it < 100;it++) {
            m_vec[it] = 1.0;
        }
    }
    Widget(double el)   {
        m_vec.resize(100);
        for(unsigned long it = 0; it < 100;it++) {
            m_vec[it] = el;
        }
    }
private:
    std::vector<double> m_vec;
};


int main(int argc, char* argv[]) {



    std::map<int,Widget> map_W;
    ptime t1 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));
    }
    ptime t2 = boost::posix_time::microsec_clock::local_time();
    time_duration diff = t2 - t1;
    std::cout << diff.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_2;
    ptime t1_2 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_2.insert(std::make_pair(it,Widget(2.0)));
    }
    ptime t2_2 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_2 = t2_2 - t1_2;
    std::cout << diff_2.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_3;
    ptime t1_3 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_3[it] = Widget(2.0);
    }
    ptime t2_3 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_3 = t2_3 - t1_3;
    std::cout << diff_3.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_0;
    ptime t1_0 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));
    }
    ptime t2_0 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_0 = t2_0 - t1_0;
    std::cout << diff_0.total_milliseconds() << std::endl;

    system("pause");
}

这分别给出了版本(我运行文件 3 次,因此每个都有 3 个连续时间差):

map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));

2198 ms、2078 ms、2072 ms

map_W_2.insert(std::make_pair(it,Widget(2.0)));

2290 ms、2037 ms、2046 ms

 map_W_3[it] = Widget(2.0);

2592 ms、2278 ms、2296 ms

 map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));

2234 ms ,2031 ms,2027 ms

因此,不同插入版本之间的结果可以忽略(尽管没有执行假设检验)!

由于使用 Widget 的默认构造函数进行初始化,map_W_3[it] = Widget(2.0); 版本在此示例中花费了大约 10-15% 的时间。

I have been running some time comparisons between the abovementioned versions:

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

Turns out that time differences between the insert versions are tiny.

#include <map>
#include <vector>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;
class Widget {
public:
    Widget() {
        m_vec.resize(100);
        for(unsigned long it = 0; it < 100;it++) {
            m_vec[it] = 1.0;
        }
    }
    Widget(double el)   {
        m_vec.resize(100);
        for(unsigned long it = 0; it < 100;it++) {
            m_vec[it] = el;
        }
    }
private:
    std::vector<double> m_vec;
};


int main(int argc, char* argv[]) {



    std::map<int,Widget> map_W;
    ptime t1 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));
    }
    ptime t2 = boost::posix_time::microsec_clock::local_time();
    time_duration diff = t2 - t1;
    std::cout << diff.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_2;
    ptime t1_2 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_2.insert(std::make_pair(it,Widget(2.0)));
    }
    ptime t2_2 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_2 = t2_2 - t1_2;
    std::cout << diff_2.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_3;
    ptime t1_3 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_3[it] = Widget(2.0);
    }
    ptime t2_3 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_3 = t2_3 - t1_3;
    std::cout << diff_3.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_0;
    ptime t1_0 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));
    }
    ptime t2_0 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_0 = t2_0 - t1_0;
    std::cout << diff_0.total_milliseconds() << std::endl;

    system("pause");
}

This gives respectively for the versions (I ran the file 3 times, hence the 3 consecutive time differences for each):

map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));

2198 ms, 2078 ms, 2072 ms

map_W_2.insert(std::make_pair(it,Widget(2.0)));

2290 ms, 2037 ms, 2046 ms

 map_W_3[it] = Widget(2.0);

2592 ms, 2278 ms, 2296 ms

 map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));

2234 ms, 2031 ms, 2027 ms

Hence, results between different insert versions can be neglected (didn't perform a hypothesis test though)!

The map_W_3[it] = Widget(2.0); version takes about 10-15 % more time for this example due to an initialization with the default constructor for Widget.

情泪▽动烟 2024-10-11 18:22:10

简而言之,[] 运算符更新值的效率更高,因为它涉及调用值类型的默认构造函数,然后为其分配新值,而 insert() 则更高效有效地增加价值。

引用 Scott Meyers 的《有效的 STL:提高标准模板库使用的 50 种具体方法》第 24 条中的片段可能会有所帮助。

template<typename MapType, typename KeyArgType, typename ValueArgType>
typename MapType::iterator
insertKeyAndValue(MapType& m, const KeyArgType&k, const ValueArgType& v)
{
    typename MapType::iterator lb = m.lower_bound(k);

    if (lb != m.end() && !(m.key_comp()(k, lb->first))) {
        lb->second = v;
        return lb;
    } else {
        typedef typename MapType::value_type MVT;
        return m.insert(lb, MVT(k, v));
    }
}

您可能决定选择一个通用的免编程版本,但重点是我发现这个范例(区分“添加”和“更新”)非常有用。

In short, [] operator is more efficient for updating values because it involves calling default constructor of the value type and then assigning it a new value, while insert() is more efficient for adding values.

The quoted snippet from Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library by Scott Meyers, Item 24 might help.

template<typename MapType, typename KeyArgType, typename ValueArgType>
typename MapType::iterator
insertKeyAndValue(MapType& m, const KeyArgType&k, const ValueArgType& v)
{
    typename MapType::iterator lb = m.lower_bound(k);

    if (lb != m.end() && !(m.key_comp()(k, lb->first))) {
        lb->second = v;
        return lb;
    } else {
        typedef typename MapType::value_type MVT;
        return m.insert(lb, MVT(k, v));
    }
}

You may decide to choose a generic-programming-free version of this, but the point is that I find this paradigm (differentiating 'add' and 'update') extremely useful.

暗喜 2024-10-11 18:22:10

如果你想在 std::map 中插入元素 - 使用 insert() 函数,如果你想找到元素(通过键)并为其分配一些元素 - 使用运算符[]。

为了简化插入,请使用 boost::assign 库,如下所示:

using namespace boost::assign;

// For inserting one element:

insert( function )( 0, 41 );

// For inserting several elements:

insert( function )( 0, 41 )( 0, 42 )( 0, 43 );

If you want to insert element in std::map - use insert() function, and if you want to find element (by key) and assign some to it - use operator[].

For simplify inserting use boost::assign library, like this:

using namespace boost::assign;

// For inserting one element:

insert( function )( 0, 41 );

// For inserting several elements:

insert( function )( 0, 41 )( 0, 42 )( 0, 43 );
渡你暖光 2024-10-11 18:22:10

我只是稍微改变一下问题(字符串映射)以显示插入的另一个兴趣:

std::map<int, std::string> rancking;

rancking[0] = 42;  // << some compilers [gcc] show no error

rancking.insert(std::pair<int, std::string>(0, 42));// always a compile error

编译器在“ranking[1] = 42;”上没有显示错误的事实可能会产生毁灭性的影响!

I just change the problem a little bit (map of strings) to show another interest of insert:

std::map<int, std::string> rancking;

rancking[0] = 42;  // << some compilers [gcc] show no error

rancking.insert(std::pair<int, std::string>(0, 42));// always a compile error

the fact that compiler shows no error on "rancking[1] = 42;" can have devastating impact !

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文