美文网首页
emplace相关知识点总结

emplace相关知识点总结

作者: appleThree | 来源:发表于2022-01-14 17:32 被阅读0次

    1 emplace与insert的异同

    1.1 相同点

    将容器中插入新成员

    emplace是C++11新标准引入了新成员,同时引入的 还有emplace_front、emplace_back。分别对应容器的原有操作insert、push_front、push_back。
    其功能分别为:将元素插入到一个指定的位置、将元素插入到容器头部、将元素插入到容器尾部。

    1.2 不同点

    是否创建临时对象,触发拷贝动作

    • 调用push或者insert时,将元素类型的对象传递出去,这些对象被拷贝到容器当中,或者创建一个局部临时对象,并将其压入容器;
    • 调用emplace时,则是将参数传递给元素类型的构造函数,emplace成员使用这些参数在容器管理的内存空间中直接构造元素,没有拷贝操作。

    1.3 示例说明

    1.3.1 源码

    #include <iostream>
    #include <string>
    #include <vector>
    
    using namespace std;
    
    //定义一个student类型
    struct student {
      student() = default;
      student(const std::string &str, int num) : name_(str), num_(num) {
          cout << name_ << " constructor called" << endl;
      }
      student(student &stu) {
          this -> name_ = stu.name_;
          this -> num_ = stu.num_;
          cout << name_ << " copy constructor called" << endl;
      }
      student(student &&stu) {
          this -> name_ = stu.name_;
          this -> num_ = stu.num_;
          cout << name_ << " move constructor called" << endl;
      }
      ~student() {
          cout << name_ << " destructor constructor called" << endl;
      }
      std::string name_;
      int num_;
    };
    
    int main() {
      vector<student> stu_vec;
      stu_vec.reserve(10); // 不提前reserve会有多次拷贝操作
      cout << "==========emplace_back right val==========" << endl;
      stu_vec.emplace_back(student("lily", 3));
      cout << stu_vec.size() << endl;//size = 1
      stu_vec.emplace_back("bob", 1);//在stu_vec末尾构造一个student对象,使用2个参数的student构造函数
      cout << stu_vec.size() << endl;//size = 2
    
      cout << "==========push_back right val=========" << endl;
      stu_vec.push_back(student("tom", 2));//正确,创建一个临时的student对象,传递给push_back
      cout << stu_vec.size() << endl;//size = 3
    //   stu_vec.push_back("tom", 2);//错误,没有接受2个参数的push_back版本,push不支持直接构造
    
      cout << "==========emplace_back left val==========" << endl;
      student stu1("mike", 4);
      stu_vec.emplace_back(stu1);
      cout << stu_vec.size() << endl;//size = 4
    
      cout << "==========push_back left val==========" << endl;
      student stu2("jeck", 5);
      stu_vec.emplace_back(stu2);
      cout << stu_vec.size() << endl;//size = 5
      return 0;
    }
    

    1.3.2 输出

    image.png

    1.3.3结论

    1)emplace和push的对象是右值

    • emplace函数在容器中直接构造,push不支持直接构造, push则是先构造一个临时对象,再把该对象拷贝到容器中,临时对象还需要析构;
    • 针对构造好的右值,emplace和push没有区别;
    • 所有针对右值,emplace的效率更高。

    2)emplace和push的对象是左值

    • emplace是直接把构造好的左值对象拷贝到容器当中
    • push也是直接把构造好的左值对象拷贝到容器当中
    • 所以针对左值,emplace和push的效率是一样的

    1.3.4 左值右值

    在C++中,一个左值是指向一个指定内存的东西。另一方面,右值就是不指向任何地方的东西。通常来说,右值是暂时和短命的,而左值则活的很久,因为他们以变量的形式(variable)存在。我们可以将左值看作为容器(container)而将右值看做容器中的事物。

    2 std::piecewise_construct_t

    2.1 简述

    c++11
    struct piecewise_construct_t { };   
    c++14
    struct piecewise_construct_t { explicit piecewise_construct_t() = default; }; 
    
    • std::piecewise_construct_t 是一个空的struct标记类型,用于区分带有两个元组参数的不同函数, 是接收两个 tuple 参数的不同函数间消歧义的空类标签类型。。
    • 不使用 std::piecewise_construct_t 的重载假定每个元组参数成为一对的元素。使用 std::piecewise_construct_t 的重载假定每个元组参数用于逐段构造指定类型的新对象,该对象将成为该pair的元素。

    2.2 std::pair

    • std::pair有一个特别的构造函数,第一个参数类型为std::piecewise_construct_t, 实际就是一个空结构体类型,用作标识。
    • std::piecewise_construct_t作用就是其字面意思:分段构造。具体来说,因pair的key和value均可以是构造函数复杂类型,因而pair的初始化相对复杂,通过带有std::piecewise_construct_t类型参数,后跟两个tuple类型参数的构造函数,用第一个tuple类型的元素来构造pair的key, 用第二个tuple类型的元素来构造pair的value,从而实现pair的初始化。
    • forward_as_tuple经常与std::piecewise_construct_t配合使用
      pair构造函数

    示例:

    #include <iostream>
    #include <utility>
    #include <tuple>
     
    struct Foo {
        Foo(std::tuple<int, float>) 
        {
            std::cout << "Constructed a Foo from a tuple\n";
        }
        Foo(int, float) 
        {
            std::cout << "Constructed a Foo from an int and a float\n";
        }
    };
     
    int main()
    {
        std::tuple<int, float> t(1, 3.14);
        std::pair<Foo, Foo> p1(t, t);
        std::pair<Foo, Foo> p2(std::piecewise_construct, t, t);
    }
    

    输出:

    Constructed a Foo from a tuple
    Constructed a Foo from a tuple
    Constructed a Foo from an int and a float
    Constructed a Foo from an int and a float
    

    2.3 std::map

    map 类型的 emplace处理比较特殊,因为和其他的容器不同,map 的 emplace 函数把它接收到的所有的参数都转发给 pair 的构造函数。对于一个 pair 来说,它既需要构造它的 key 又需要构造它的 value。如果我们按照普通的语法使用变参模板,我们无法区分哪些参数用来构造 key, 哪些用来构造 value。 比如下面的代码:

    map<string, complex<double>> scp;
    scp.emplace("hello", 1, 2); // 无法区分哪个参数用来构造 key 哪些用来构造 value
    // string s("hello", 1), complex<double> cpx(2) ???
    // string s("hello"), complex<double> cpx(1, 2) ???
    

    所以我们需要一种方式既可以接受异构变长参数,又可以区分 key 和 value,解决 方式是使用 C++11 中提供的 tuple。

    pair<string, complex<double>> scp(make_tuple("hello"), make_tuple(1, 2));
    

    然后这种方式是有问题的,因为这里有歧义,第一个 tuple 会被当成是 key,第二 个tuple会被当成 value。最终的结果是类型不匹配而导致对象创建失败,为了解决 这个问题,C++11 设计了 piecewise_construct_t 这个类型用于解决这种歧义,它 是一个空类,存在的唯一目的就是解决这种歧义,全局变量 std::piecewise_construct 就是该类型的一个变量。所以最终的解决方式如下:

    pair<string, complex<double>> scp(piecewise_construct, make_tuple("hello"), make_tuple(1, 2));
    

    当然因为 map 的 emplace 把参数原样转发给 pair 的构造,所以你需要使用同样 的语法来完成 emplace 的调用,当然你可以使用 forward_as_tuple 替代 make_tuple,该函数会帮你构造一个 tuple 并转发给 pair 构造。

    取舍

    map<string, complex<double>> scp;
    emplace方式:scp.emplace(piecewise_construct,forward_as_tuple("hello"),forward_as_tuple(1, 2));
    insert方式:scp.insert({"world", {1, 2}});
    
    • 对于emplace方式来说,虽然避免了临时变量的构造,但是却需要构建两个 tuple 。
    • 从方便性和代码优雅性上来说,insert方式要胜过 emplace方式。
    • 因此对于临时变量构建代价不是很大的对象(比如基础类型)推荐使用 insert 而不是 emplace。

    参考:
    https://www.jianshu.com/p/94b0221f64a5
    https://blog.csdn.net/wangmj_hdu/article/details/119537411
    https://blog.csdn.net/luoshabugui/article/details/118696418
    https://www.cnblogs.com/guxuanqing/p/11396511.html

    相关文章

      网友评论

          本文标题:emplace相关知识点总结

          本文链接:https://www.haomeiwen.com/subject/gpbacrtx.html