C++11 emplace_back 和 push_back

C++11 emplace_back 和 push_back

Reference:
1.《C++ Primer Plus》Stephen Prata

C++11 新增的可变参数模板(vatiadic template)函数参数包(parameter pack) 使得可以提供就地创建(emplacement) 方法。这意味着什么呢?与移动语义一样,就地创建旨在提高效率。看下面的代码段:

class Items
{
	double x;
	double y;
	int m;
public:
	Items();								//#1
	Items(double xx, double yy, int mm);	//#2
	...
}...
vector<Items> vt(10);
...
vt.push_back(Items(8.2, 2.8, 3));

调用 insert() 将导致内存分配函数在 vt 末尾创建一个默认 Items 对象(push_back()调用的insert)。接下来,构造函数 Items() 创建一个临时 Items 变量,该对象被复制到 vt 的开头,然后被删除。在 C++ 中,可以这样做:

vi.emplace_back(8.2, 2.8, 3);

方法 emplace_back() 是一个可变参数模板,将一个函数参数包作为参数:

template <class... Args> void emplace_back(Args&&... args);

在这里可以看到,emplace_back()push_back() 的本质区别在于:

  1. 使用了右值引用;
  2. 使用了可变参数模板。

上述三个实参(8.2, 2.8, 3)将被封装到参数 args 中。参数 args 被传递给内存分配函数,而内存分配函数将其展开,并使用接收三个参数的 Items 构造函数(#2),而不是默认构造函数(#1)。也就是说,它使用 Items(args…),这里将展开为 Items(8.2, 2.8, 3)。因此,将在矢量中就地创建所需的对象,而不是创建一个临时变量,再将其复制到矢量中(这一点很重要)。

来看一个示例:

#include <iostream>
#include <vector>
#include <string>

class MyObject {
public:
    MyObject(std::string value) : data(value) {
        std::cout << "Constructor: " << data << std::endl;
    }
    
    MyObject(const MyObject& other) : data(other.data) {
        std::cout << "Copy Constructor: " << data << std::endl;
    }

    MyObject(MyObject&& other) : data(std::move(other.data)) {
        std::cout << "Move Constructor: " << data << std::endl;
    }
    std::string data;

private:
};

int main() {
    std::vector<MyObject> myObjects;
    myObjects.reserve(10);

    std::string value1 = "1";
    myObjects.emplace_back(std::move(value1)); // 使用 std::move 来移动 value1 的所有权
    std::cout << "value1:" << value1 << std::endl;

    std::string value2 = "22";
    myObjects.emplace_back(value2);
    std::cout << "value2:" << value2 << std::endl;

    std::string value3 = "333";
    MyObject obj(value3);
    myObjects.emplace_back(obj);
    std::cout << "value3:" << value3 << std::endl;
    std::cout << "obj.data:" << obj.data << std::endl;

    std::string value4 = "4444";
    MyObject obj_(value4);
    myObjects.emplace_back(std::move(obj_));
    std::cout << "value4:" << value4 << std::endl;
    std::cout << "obj_.data:" << obj_.data << std::endl;

    std::string value5 = "55555";
    myObjects.push_back(std::move(value5));

    std::string value6 = "666666";
    myObjects.push_back(value6);

    std::string value7 = "7777777";
    MyObject obj__(value7);
    myObjects.push_back(obj__);

    std::string value8 = "88888888";
    MyObject obj___(value8);
    myObjects.push_back(std::move(obj___));

    return 0;
}

有以下输出:

Constructor: 1
value1:

Constructor: 22
value2:22

Constructor: 333
Copy Constructor: 333
value3:333
obj.data:333

Constructor: 4444
Move Constructor: 4444
value4:4444
obj_.data:

Constructor: 55555
Move Constructor: 55555

Constructor: 666666
Move Constructor: 666666

Constructor: 7777777
Copy Constructor: 7777777

Constructor: 88888888
Move Constructor: 88888888
  • 1 1 1 中对 std::string 使用移动构造函数 ,它将值赋予了在矢量中创建的 MyObject 对象,因此在此打印 value1 的时候无输出,MyObject 对象的创建使用的是初始化构造函数(因为是对string做的move,所以value1没有值了,这时用一个string创建对象,那肯定使用的是初始化构造函数);
  • 2 2 2 中在构造 MyObject 对象时使用的也是初始化构造函数,但是 std::string 的使用是复制构造函数,因此这次打印 value2 的时候是有值的(同上,但因为value2在初始化构造函数内使用的是复制构造函数,所以后面的打印是有值的);
  • 3 3 3 4 4 4 先直接使用初始化构造函数创建了 MyObject,因此均会先打印一个 Constructor: “string_value”。这时对 emplace_back 传递的是 obj ----- 一个左值,所以会调用 MyObject 的复制构造函数
  • 4 4 4 中对 obj_ 额外使用了 std::move(),将对象的类型强制转换为了右值,这时就会使用移动构造函数了,在移动构造函数内使用了一个 data(std::move(other.data)),毕竟都移动构造了,那么肯定数据是需要转移的,所以在这里成员变量的初始化也同时使用了 std::string 的移动构造函数,反应出来的结果就是打印 obj_.data 时的是没有输出的;
  • 5 5 5 6 6 6 均先使用初始化构造函数先创建了一个 MyObject 对象,但由于 push_back() 的输入是左值,会先创建一个临时变量,再将其复制到矢量中,因此这时还会多一个构造过程。又因为这个构造过程是在 emplace_back() 内部发生的,会将临时变量看成一个右值,调用移动构造函数 5 5 5 6 6 6 的区别同上,是 MyObject 的成员变量在初始化时,使用的是移动构造函数还是复制构造函数的区别;
  • 7 7 7 8 8 8 3 3 3 4 4 4,这时 emplace_back()push_back() 的使用并无差异。

还可以注意的一点是,如果 MyObject 没有定义移动构造函数,编译器将使用复制构造函数。如果也没有定义复制构造函数,将根本不允许上述赋值。
还是举这个例子,删去 MyObject(MyObject&& other)

#include <iostream>
#include <vector>
#include <string>

class MyObject {
public:
    MyObject(std::string value) : data(value) {
        std::cout << "Constructor: " << data << std::endl;
    }
    
    MyObject(const MyObject& other) : data(other.data) {
        std::cout << "Copy Constructor: " << data << std::endl;
    }

    // MyObject(MyObject&& other) : data(std::move(other.data)) {
    //     std::cout << "Move Constructor: " << data << std::endl;
    // }
    std::string data;

private:
};

int main() {
    std::vector<MyObject> myObjects;
    myObjects.reserve(10);

    std::string value1 = "1";
    myObjects.emplace_back(std::move(value1)); // 使用 std::move 来移动 value1 的所有权
    std::cout << "value1:" << value1 << std::endl;

    std::string value2 = "22";
    myObjects.emplace_back(value2);
    std::cout << "value2:" << value2 << std::endl;

    std::string value3 = "333";
    MyObject obj(value3);
    myObjects.emplace_back(obj);
    std::cout << "value3:" << value3 << std::endl;
    std::cout << "obj.data:" << obj.data << std::endl;

    std::string value4 = "4444";
    MyObject obj_(value4);
    myObjects.emplace_back(std::move(obj_));
    std::cout << "value4:" << value4 << std::endl;
    std::cout << "obj_.data:" << obj_.data << std::endl;

    std::string value5 = "55555";
    myObjects.push_back(std::move(value5));

    std::string value6 = "666666";
    myObjects.push_back(value6);

    std::string value7 = "7777777";
    MyObject obj__(value7);
    myObjects.push_back(obj__);

    std::string value8 = "88888888";
    MyObject obj___(value8);
    myObjects.push_back(std::move(obj___));

    return 0;
}

输出为:

Constructor: 1
value1:

Constructor: 22
value2:22

Constructor: 333
Copy Constructor: 333
value3:333
obj.data:333

Constructor: 4444
Copy Constructor: 4444
value4:4444
obj_.data:4444

Constructor: 55555
Copy Constructor: 55555

Constructor: 666666
Copy Constructor: 666666

Constructor: 7777777
Copy Constructor: 7777777

Constructor: 88888888
Copy Constructor: 88888888

经过比较可以发现,在之前所有使用 Move Constructor 的地方,都换成了 Copy Constructor。

Complement

  1. 很多官方模板类对移动构造函数即移动赋值函数做了定义,如 shared_ptr:
      __shared_ptr(__shared_ptr&& __r) noexcept
      : _M_ptr(__r._M_ptr), _M_refcount()
      {
    _M_refcount._M_swap(__r._M_refcount);
    __r._M_ptr = 0;
      }
    
    所以可以这样使用使程序更优雅:
    std::vector<std::shared_ptr<MyObject>> myObjects;
    std::shared_ptr<MyObject> obj(new MyObject("000"));
    myObjects.emplace_back(std::move(obj));
    
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泠山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值