代码之家  ›  专栏  ›  技术社区  ›  fredoverflow

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

  •  84
  • fredoverflow  · 技术社区  · 14 年前

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

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

    8 回复  |  直到 5 年前
        1
  •  80
  •   user229044    14 年前

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

    • 这个 运算符[] 搜索 对于密钥,请插入 默认构造 如果找不到值,则返回一个要为其赋值的引用。显然,如果 mapped_type 可以从直接初始化而不是默认构造和分配中获益。此方法还使您无法确定是否确实发生了插入,或者您是否只覆盖了先前插入的键的值
    • 这个 插入 如果密钥已经存在于映射中,并且尽管它经常被遗忘,但成员函数将不起作用 std::pair<iterator, bool> 这可能会引起兴趣(最明显的是确定插入是否实际完成)。

    从所有列出的可能性中 插入 ,三个都是 几乎 相当于。提醒一下,让我们看看 插入 标准中的签名:

    typedef pair<const Key, T> value_type;
    
      /* ... */
    
    pair<iterator, bool> insert(const value_type& x);
    

    三个电话有什么不同?

    • std::make_pair 依赖于模板参数推导,并且可以(在本例中 )生产与实际不同类型的产品 value_type 在地图上,这将需要一个额外的调用 std::pair 模板构造函数以便转换为 值类型 (即:添加 const first_type )
    • std::pair<int, int> 还需要对 std::对 以便将参数转换为 值类型 (即:添加 常量 第一种 )
    • std::map<int, int>::value_type 毫无疑问,因为它是 插入 成员函数。

    最后,我会避免使用 运算符[] 当目标是插入时,除非在默认构建和分配 映射类型 ,我不关心是否已有效插入新密钥。使用时 插入 ,构造 值类型 可能是个好办法。

        2
  •  84
  •   Geoff Romer    9 年前

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

    function.insert({0, 42});
    

    这在功能上等同于

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

    但要简洁易懂得多。正如其他答案所指出的,与其他形式相比,这有几个优点:

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

    主要的缺点是,此表单过去要求密钥和值是可复制的,因此它不能用于例如 unique_ptr 价值观。在标准中已修复,但修复可能尚未达到标准库实现。

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

    function.emplace(0, 42);
    

    这比任何形式的 插入() ,可用于仅移动类型,如 独特的 ,理论上可能会稍微高效一些(尽管一个好的编译器应该优化消除差异)。唯一的缺点是它可能会让你的读者有点吃惊,因为 emplace 方法通常不是这样使用的。

        3
  •  9
  •   In silico    14 年前

    第一个版本:

    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
    

    另一方面,如果钥匙 地图上已经存在。如果密钥不存在,则插入密钥/值对。

    这三个insert函数几乎相同。 std::map<int, int>::value_type typedef 对于 std::pair<const int, int> ,和 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());
    
        4
  •  3
  •   user3116431    11 年前

    我对上述版本进行了一些时间比较:

    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毫秒,2078毫秒,2072毫秒

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

    2290毫秒,2037毫秒,2046毫秒

     map_W_3[it] = Widget(2.0);
    

    2592毫秒,2278毫秒,2296毫秒

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

    2234毫秒,2031毫秒,2027毫秒

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

    这个 map_W_3[it] = Widget(2.0); 对于这个例子,由于使用小部件的默认构造函数进行初始化,版本需要大约10-15%的时间。

        5
  •  2
  •   galactica    6 年前

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

    引用的片段来自 有效的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));
        }
    }
    

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

        6
  •  1
  •   Viktor Sehr    14 年前

    如果要用键0覆盖元素

    function[0] = 42;
    

    否则:

    function.insert(std::make_pair(0, 42));
    
        7
  •  1
  •   David Szalai user1701047    11 年前

    如果要在std::map-use insert()函数中插入元素,如果要查找元素(按键)并为其分配一些元素,请使用operator[]。

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

    using namespace boost::assign;
    
    // For inserting one element:
    
    insert( function )( 0, 41 );
    
    // For inserting several elements:
    
    insert( function )( 0, 41 )( 0, 42 )( 0, 43 );
    
        8
  •  1
  •   jo_    9 年前

    我只是稍微改变一下这个问题(字符串映射)来表示对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
    

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

        9
  •  0
  •   honk    5 年前

    自从 C++17 std::map 提供两种新的插入方法: insert_or_assign() try_emplace() ,正如 comment by sp2danny .

    插入或分配()

    基本上, 插入或分配() operator[] . 与…对比 运算符[] , 插入或分配() 不要求映射的值类型是默认可构造的。例如,以下代码无法编译,因为 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() , 插入或分配() 返回 pair<iterator, bool> . 布尔值是 true 如果插入发生并且 false 如果任务完成了。迭代器指向插入或更新的元素。

    尝试_emplace()

    与上述类似, 尝试_emplace() 是对 emplace() . 与…对比 安放() , 尝试_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

    Code on Coliru

    请注意:我尽量使我的解释简短明了,以符合这个答案。为了更精确和全面的描述,我建议阅读 this article Fluent C++ .