C++ 在c+中插入vs emplace vs operator[]+;地图

C++ 在c+中插入vs emplace vs operator[]+;地图,c++,dictionary,insert,operators,emplace,C++,Dictionary,Insert,Operators,Emplace,我第一次使用贴图,我意识到有很多方法可以插入元素。您可以使用emplace()、operator[]或insert(),以及使用value\u type或make\u pair等变体。虽然有很多关于所有这些案例的信息和关于特定案例的问题,但我仍然无法理解全局。 因此,我的两个问题是: 它们各自的优势是什么 是否需要在标准中添加emplace?没有它之前有什么是不可能的吗 Emplace:利用右值引用使用已创建的实际对象。这意味着不调用复制或移动构造函数,这对大型对象很好!O(log(N))时间

我第一次使用贴图,我意识到有很多方法可以插入元素。您可以使用
emplace()
operator[]
insert()
,以及使用
value\u type
make\u pair等变体。虽然有很多关于所有这些案例的信息和关于特定案例的问题,但我仍然无法理解全局。
因此,我的两个问题是:

  • 它们各自的优势是什么

  • 是否需要在标准中添加emplace?没有它之前有什么是不可能的吗


  • Emplace:利用右值引用使用已创建的实际对象。这意味着不调用复制或移动构造函数,这对大型对象很好!O(log(N))时间

    插入:具有标准左值引用和右值引用的重载,以及要插入的元素列表的迭代器,以及元素所属位置的“提示”。使用“提示”迭代器可以将插入所花费的时间缩短到contant time,否则就是O(log(N))时间

    运算符[]:检查对象是否存在,如果存在,则修改对此对象的引用,否则使用提供的键和值在两个对象上调用make_pair,然后执行与insert函数相同的工作。这是O(log(N))时间

    结对:只不过是结对而已


    没有“需要”在标准中添加emplace。在c++11中,我相信添加了&&类型的引用。这消除了移动语义的必要性,并允许优化某些特定类型的内存管理。特别是右值引用。重载的insert(value_type&&)运算符没有利用就地语义,因此效率要低得多。虽然它提供了处理右值引用的能力,但它忽略了它们的关键用途,即对象的就地构造。

    除了优化机会和更简单的语法之外,插入和定位之间的一个重要区别是后者允许显式转换。(这涉及整个标准库,而不仅仅是地图。)

    下面是一个示例来演示:

    #include <vector>
    
    struct foo
    {
        explicit foo(int);
    };
    
    int main()
    {
        std::vector<foo> v;
    
        v.emplace(v.end(), 10);      // Works
        //v.insert(v.end(), 10);     // Error, not explicit
        v.insert(v.end(), foo(10));  // Also works
    }
    
    #包括
    结构foo
    {
    显式foo(int);
    };
    int main()
    {
    std::向量v;
    v、 安放(v.end(),10);//工作
    //v、 insert(v.end(),10);//错误,不明确
    v、 insert(v.end(),foo(10));//也可以
    }
    

    诚然,这是一个非常具体的细节,但在处理用户定义的转换链时,需要记住这一点。

    在地图的特定情况下,旧的选项只有两个:
    操作符[]
    插入
    (插入
    的不同风格)。所以我将开始解释这些

    运算符[]
    是一个查找或添加运算符。它将尝试在映射内找到具有给定键的元素,如果该元素存在,它将返回对存储值的引用。如果没有,它将使用默认初始化创建一个插入到位的新元素,并返回对它的引用

    insert
    函数(在单元素风格中)采用
    value\u type
    std::pair
    ),它使用键(
    first
    成员)并尝试插入它。因为
    std::map
    不允许重复,如果存在现有元素,它将不会插入任何内容

    两者之间的第一个区别是
    操作符[]
    需要能够构造默认初始化值,因此对于无法默认初始化的值类型,它是不可用的。二者之间的第二个区别是,当已经存在具有给定键的元素时会发生什么。
    insert
    函数不会修改映射的状态,而是向元素返回一个迭代器(以及一个
    false
    表示未插入该元素)

    但事实并非如此。。。[1] 和[2]实际上是等价的。在这两种情况下,代码都会创建一个相同类型的临时对象(
    std::pair
    ),并将其传递给
    insert
    函数。
    insert
    函数将在二进制搜索树中创建适当的节点,然后将
    value\u type
    部分从参数复制到节点。使用
    value\u-type
    的优点是,
    value\u-type
    总是匹配
    value\u-type
    ,您不能错误键入
    std::pair
    参数的类型

    区别在于[3]。函数
    std::make_pair
    是一个模板函数,它将创建一个
    std::pair
    。签名为:

    template <typename T, typename U>
    std::pair<T,U> make_pair(T const & t, U const & u );
    
    但这仍然容易出错,就像在案例[1]中显式键入类型一样

    到目前为止,我们有不同的调用
    insert
    的方法,需要在外部创建
    value\u type
    ,并将该对象的副本复制到容器中。或者,如果类型是默认的可构造和可赋值类型(有意只关注于
    m[k]=v
    ),并且它需要一个对象的默认初始化和值到该对象中的副本,则可以使用
    operator[]

    在C++11中,通过可变模板和完美转发,有了一种通过放置(就地创建)将元素添加到容器中的新方法。不同容器中的
    emplace
    函数的作用基本相同:该函数不获取复制到容器中的源,而是获取将转发到容器中存储的对象的构造函数的参数

    m.emplace(t,u);               // 5
    
    在[5]中,不会创建
    std::pair
    并将其传递给
    emplace
    ,而是将对
    t
    u
    对象的引用传递给
    emplace
    template <typename T, typename U>
    std::pair<T,U> make_pair(T const & t, U const & u );
    
    m.insert( std::make_pair<const K,V>(t,u) );  // 4
    
    m.emplace(t,u);               // 5
    
    #include <iostream>
    #include <unordered_map>
    #include <utility>
    
    //Foo simply outputs what constructor is called with what value.
    struct Foo {
      static int foo_counter; //Track how many Foo objects have been created.
      int val; //This Foo object was the val-th Foo object to be created.
    
      Foo() { val = foo_counter++;
        std::cout << "Foo() with val:                " << val << '\n';
      }
      Foo(int value) : val(value) { foo_counter++;
        std::cout << "Foo(int) with val:             " << val << '\n';
      }
      Foo(Foo& f2) { val = foo_counter++;
        std::cout << "Foo(Foo &) with val:           " << val
                  << " \tcreated from:      \t" << f2.val << '\n';
      }
      Foo(const Foo& f2) { val = foo_counter++;
        std::cout << "Foo(const Foo &) with val:     " << val
                  << " \tcreated from:      \t" << f2.val << '\n';
      }
      Foo(Foo&& f2) { val = foo_counter++;
        std::cout << "Foo(Foo&&) moving:             " << f2.val
                  << " \tand changing it to:\t" << val << '\n';
      }
      ~Foo() { std::cout << "~Foo() destroying:             " << val << '\n'; }
    
      Foo& operator=(const Foo& rhs) {
        std::cout << "Foo& operator=(const Foo& rhs) with rhs.val: " << rhs.val
                  << " \tcalled with lhs.val = \t" << val
                  << " \tChanging lhs.val to: \t" << rhs.val << '\n';
        val = rhs.val;
        return *this;
      }
    
      bool operator==(const Foo &rhs) const { return val == rhs.val; }
      bool operator<(const Foo &rhs)  const { return val < rhs.val;  }
    };
    
    int Foo::foo_counter = 0;
    
    //Create a hash function for Foo in order to use Foo with unordered_map
    namespace std {
       template<> struct hash<Foo> {
           std::size_t operator()(const Foo &f) const {
               return std::hash<int>{}(f.val);
           }
       };
    }
    
    int main()
    {
        std::unordered_map<Foo, int> umap;  
        Foo foo0, foo1, foo2, foo3;
        int d; //Some int that will be umap's value. It is not important.
    
        //Print the statement to be executed and then execute it.
    
        std::cout << "\numap.insert(std::pair<Foo, int>(foo0, d))\n";
        umap.insert(std::pair<Foo, int>(foo0, d));
        //Side note: equiv. to: umap.insert(std::make_pair(foo0, d));
    
        std::cout << "\numap.insert(std::move(std::pair<Foo, int>(foo1, d)))\n";
        umap.insert(std::move(std::pair<Foo, int>(foo1, d)));
        //Side note: equiv. to: umap.insert(std::make_pair(foo1, d));
        
        std::cout << "\nstd::pair<Foo, int> pair(foo2, d)\n";
        std::pair<Foo, int> pair(foo2, d);
    
        std::cout << "\numap.insert(pair)\n";
        umap.insert(pair);
    
        std::cout << "\numap.emplace(foo3, d)\n";
        umap.emplace(foo3, d);
        
        std::cout << "\numap.emplace(11, d)\n";
        umap.emplace(11, d);
    
        std::cout << "\numap.insert({12, d})\n";
        umap.insert({12, d});
    
        std::cout.flush();
    }
    
    Foo() with val:                0
    Foo() with val:                1
    Foo() with val:                2
    Foo() with val:                3
    
    umap.insert(std::pair<Foo, int>(foo0, d))
    Foo(Foo &) with val:           4    created from:       0
    Foo(Foo&&) moving:             4    and changing it to: 5
    ~Foo() destroying:             4
    
    umap.insert(std::move(std::pair<Foo, int>(foo1, d)))
    Foo(Foo &) with val:           6    created from:       1
    Foo(Foo&&) moving:             6    and changing it to: 7
    ~Foo() destroying:             6
    
    std::pair<Foo, int> pair(foo2, d)
    Foo(Foo &) with val:           8    created from:       2
    
    umap.insert(pair)
    Foo(const Foo &) with val:     9    created from:       8
    
    umap.emplace(foo3, d)
    Foo(Foo &) with val:           10   created from:       3
    
    umap.emplace(11, d)
    Foo(int) with val:             11
    
    umap.insert({12, d})
    Foo(int) with val:             12
    Foo(const Foo &) with val:     13   created from:       12
    ~Foo() destroying:             12
    
    ~Foo() destroying:             8
    ~Foo() destroying:             3
    ~Foo() destroying:             2
    ~Foo() destroying:             1
    ~Foo() destroying:             0
    ~Foo() destroying:             13
    ~Foo() destroying:             11
    ~Foo() destroying:             5
    ~Foo() destroying:             10
    ~Foo() destroying:             7
    ~Foo() destroying:             9
    
    std::cout << "\numap.insert({{" << Foo::foo_counter << ", d}})\n";
    umap.insert({{Foo::foo_counter, d}});
    //but umap.emplace({{Foo::foo_counter, d}}); results in a compile error!
    
    std::cout << "\numap.insert(std::pair<const Foo, int>({" << Foo::foo_counter << ", d}))\n";
    umap.insert(std::pair<const Foo, int>({Foo::foo_counter, d}));
    //The above uses Foo(int) and then Foo(const Foo &), as expected. but the
    // below call uses Foo(int) and the move constructor Foo(Foo&&). 
    //Do you see why?
    std::cout << "\numap.insert(std::pair<Foo, int>({" << Foo::foo_counter << ", d}))\n";
    umap.insert(std::pair<Foo, int>({Foo::foo_counter, d}));
    //Not only that, but even more interesting is how the call below uses all 
    // three of Foo(int) and the Foo(Foo&&) move and Foo(const Foo &) copy 
    // constructors, despite the below call's only difference from the call above 
    // being the additional { }.
    std::cout << "\numap.insert({std::pair<Foo, int>({" << Foo::foo_counter << ", d})})\n";
    umap.insert({std::pair<Foo, int>({Foo::foo_counter, d})});
    
    
    //Pay close attention to the subtle difference in the effects of the next 
    // two calls.
    int cur_foo_counter = Foo::foo_counter;
    std::cout << "\numap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}}) where " 
      << "cur_foo_counter = " << cur_foo_counter << "\n";
    umap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}});
    
    std::cout << "\numap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}}) where "
      << "Foo::foo_counter = " << Foo::foo_counter << "\n";
    umap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}});
    
    
    //umap.insert(std::initializer_list<std::pair<Foo, int>>({{Foo::foo_counter, d}}));
    //The call below works fine, but the commented out line above gives a 
    // compiler error. It's instructive to find out why. The two calls
    // differ by a "const".
    std::cout << "\numap.insert(std::initializer_list<std::pair<const Foo, int>>({{" << Foo::foo_counter << ", d}}))\n";
    umap.insert(std::initializer_list<std::pair<const Foo, int>>({{Foo::foo_counter, d}}));