C++11 emplace_back 和 push_back

C++11 emplace_back 和 push_back

  • Complement

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 
#include 
#include 

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 对象的创建使用的是初始化构造函数
  • 2 2 2 中在构造 MyObject 对象时使用的也是初始化构造函数,但是 std::string 的使用是复制构造函数,因此这次打印 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 
#include 
#include 

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

你可能感兴趣的:(C/C++,c++,算法)