emplace 关键字是 C++11 的一个新特性。emplace_back()push_abck() 的区别是:push_back() 在向 vector 尾部添加一个元素时,首先会创建一个临时对象,然后再将这个临时对象移动或拷贝到 vector 中(如果是拷贝的话,事后会自动销毁先前创建的这个临时元素);而 emplace_back() 在实现时,则是直接在 vector 尾部创建这个元素,省去了移动或者拷贝元素的过程。

但是 emplace_back() 的这个特性是在任何场景都生效的吗?

本文对 vector 的 emplace_back() 方法的使用进行简单实验,加深对 emplace_back() 的理解,在使用时更得心应手。

之前也了解到 emplace_back() 方法比 push_back() 方法效率高,原因是 emplace_back() 在向 vector 中插入元素时比 push_back() 少了一次移动构造或拷贝构造。但 emplace_back() 并不是任何场景效率都比 push_back() 高。

先说 结论

假设 vector 中元素类型是类类型,那么 emplace_back() 待添加的元素的类型是类中有参构造的参数类型时,emplace_back()push_back() 少一次移动或拷贝构造函数。而如果添加的元素是类类型的对象时,则和 push_back() 一样都只会调用一次移动构造函数或一次拷贝构造函数。

如下示例:

class Person {
public:
    int num;

public:
    Person (int num) {
        cout << __PRETTY_FUNCTION__ << endl;
        this->num = num;
    }

    Person (const Person & p) {
        cout << __PRETTY_FUNCTION__ << endl;
        this->num = p.num;
        cout << "num=" << this->num << endl;
    }

    Person (Person && p) noexcept {
        cout << __PRETTY_FUNCTION__ << endl;
        this->num = p.num;
        p.num = 0;
        cout << "num=" << this->num << endl;
    }
    
    ~Person () {
        cout << __PRETTY_FUNCTION__ << endl;
    }
};

void test01() {

    Person p1(1);
    vector<Person> v;

    cout << __LINE__ << endl;
    v.push_back(10);
    v.push_back(10);
    v.push_back(10);
    v.push_back(10);
    v.push_back(10);
    v.push_back(10);
    v.push_back(10);
    v.push_back(10);
    v.push_back(10);
    
    // 上面这些操作,是为了扩充 vector 的容量,从而不受容量已满开辟新空间时移动元素时调用相关构造函数对下面代码的影响

    cout << __LINE__ << endl;  // a
    v.push_back(10);  // 会调用一次有参构造+一次移动构造

    cout << __LINE__ << endl;
    v.emplace_back(20);  // 会调用一次有参构造

    cout << __LINE__ << endl;
    v.push_back(p1);  // 会调用一次拷贝构造
    
    cout << __LINE__ << endl;
    v.emplace_back(p1);  // 会调用一次拷贝构造
                               // b
}

int main(){
    test01();
    return 0;
}

上述代码 a-b 之间的执行结果如下:

175
Person::Person(int)
Person::Person(Person &&)
num=10
Person::~Person()
178
Person::Person(int)
181
Person::Person(const Person &)
num=1
184
Person::Person(const Person &)
num=1

可结合代码中的注释来理解执行结果。

由上实验可以看出:在向 vector 中添加元素时(假设元素类型是一个类类型):

  • 如果添加的是类类型的有参构造函数的参数类型对应的变量:
    • 如果是通过 push_back() 方式添加的,则会调用 一次有参构造 + 一次移动构造 ,如果移动构造不可用则为拷贝构造
    • 如果是通过 emplace_back() 方式添加的,则只会调用 一次有参构造
  • 如果添加的是类类型的变量:
    • 不论是通过 push_back() 方式还是 emplace_back() 方式,都只会调用 一次拷贝构造

另外,对上述测试代码做相关解释:

  1. 在定义一个 vector 之后,向其 push_back() 9 个元素的原因是:由于 vector 的容量是动态增长的(2倍于之前的容量),所以向 vector 中添加 9 个元素,使其容量扩大到 16 ,这样后续的代码在执行时则不会受到 vector 开辟新空间时需要将原来空间的元素移动到新空间时调用相关构造函数的影响,如不理解可参考代码的执行结果。

之前以为 emplace_back() 不论是插入何种元素都是会调用一次有参构造+一次移动或拷贝构造,但是现在发现这其实是不准确的。另外,我本人在使用时其实是不会以有参构造中的参数类型的变量作为实参来使用 push_back() 的,个人感觉这种方式不易于理解,我更习惯于直接使用直接添加类类型的变量这种方式。但这种方式需要提前构造好待添加的类对象,这个操作也会调用一次构造函数,所以直接添加类类型的变量并不会提高效率。所以如果想要提高效率可以使用如下方式:

  • emplace_back(int num) 只会调用一次移动构造或拷贝构造
  • push_back(std::move(Person p))emplace_back(std::move(Person p)) 只会调用一次移动构造或拷贝构造
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐