[代码][C++] vector中emplace的使用区别

看了很多教程发现大家的说法不太一致,贴一下我自己在vscode里gcc编译的代码,把想看的场景排列组合了,有一些罗嗦,

结论

  • 对于传入的左值对象(或临时右值对象等已经构造好的对象),emplace和push或者insert的行为是一样的,都是一次拷贝构造(或移动构造)
  • emplace可以接受用于构造对象的参数,而push和insert必须显式传入已经构造好的对象(临时右值或左值对象),因此在这种场景下避免了部分临时对象的开销
    • emplace依赖带参的构造函数,如果没有相应构造函数,直接构造的作用就失效了
    • emplace的优势在少量(或者直接传入构造好的对象)元素的传入中并不明显,应用场景是大量复杂数据(而不是对象)
  • 对于插入操作,先根据传入对象完成临时对象的拷贝(或移动),再从末开始依次向后移动(构造)尾端元素,再将临时对象插入到实际位置

用于测试的类:Student

class Student {
public:
    Student() { cout << " 默认构造函数 " << endl; }
    Student(const string& name, int age):_name(name), _age(age) { cout << " 构造函数:" << _name << endl; }
    Student(const Student& stu):_name(stu._name), _age(stu._age) { cout << " 拷贝构造:" << _name << endl; }
    Student(Student &&stu) : _name(move(stu._name)), _age(move(stu._age)) { cout << " 移动构造函数:" << _name << endl; }

    Student& operator=(const Student& stu){
        _name = stu._name; 
        _age = stu._age;
        cout << " 拷贝构造函数:" << _name << endl;
        return *this;
    }

    Student& operator=(Student&& stu){
        _name = move(stu._name); 
        _age = move(stu._age);
        cout << " 移动构造函数:" << _name << endl;
        return *this;
    }

private:
    string _name;
    int _age;
};

具体的测试及输出:

int main() {
    Student tom("tom", 25); //构造函数tom
    Student tom1("tom1", 25); //构造函数tom1
    Student tom2("tom2", 25); //构造函数tom2
    Student tom3("tom3", 25); //构造函数tom3
    
    vector<Student> v1; 
    v1.reserve(20);
    
    v1.push_back(tom); //拷贝构造函数tom
    v1.emplace_back(tom); //拷贝构造函数tom
    v1.push_back(move(tom)); //移动构造函数tom
    Student tom4("tom4", 25); //构造函数tom4
    v1.emplace_back(move(tom4)); //移动构造函数tom4

    v1.insert(v1.begin() + v1.size(), tom1); //向尾部插入,无需要移动的元素,直接拷贝构造tom1
    v1.emplace(v1.begin() + v1.size(), tom1); //向尾部插入,无需要移动的元素,直接拷贝构造tom1
    v1.insert(v1.begin(), tom2); //先拷贝tom2构造临时对象,再移动(构造)尾端元素,再插入位置;包括tom3在内的size()次移动构造
    v1.emplace(v1.begin(), tom3); //先拷贝tom3构造临时对象,再依次移动(构造)尾端元素,再插入位置;包括tom3在内的size()次移动构造
    
    v1.push_back(Student("tom5", 26)); //构造函数(tom5的临时对象);移动构造函数(tom5)
    v1.emplace_back("tom6", 27); //构造函数tom6

    v1.insert(v1.begin() + v1.size(), Student("tom7", 28)); //构造函数(tom7的临时对象);移动构造函数(tom7)
    v1.emplace(v1.begin() + v1.size(), "tom8", 29);         //构造函数;

    v1.insert(v1.begin() + v1.size()-1, Student("tom9", 28)); //构造函数;移动构造函数(尾端元素 + tom9)
    v1.emplace(v1.begin() + v1.size() - 1, "tom10", 29);       //构造函数;移动构造函数(尾端元素 + tom10)
    return 0;
}

下面是根据cppreference给出的官方例子做的实验:

#include <vector>
#include <string>
#include <iostream>
 
struct President
{
    std::string name;
    std::string country;
    int year;
 
    President(std::string p_name, std::string p_country, int p_year)
        : name(std::move(p_name)), country(std::move(p_country)), year(p_year)
    {
        std::cout << "I am being constructed.\n";
    }
    President(President&& other)
        : name(std::move(other.name)), country(std::move(other.country)), year(other.year)
    {
        std::cout << "I am being moved.\n";
    }
    President& operator=(const President& other) = default;
};
 
int main()
{
    std::vector<President> elections;
    std::cout << "emplace_back:\n";
    elections.emplace_back("Nelson Mandela", "South Africa", 1994); //可以接受一组参数用于初始化类对象
 
    std::vector<President> reElections;
    std::cout << "\npush_back:\n";
    reElections.push_back(President("Franklin Delano Roosevelt", "the USA", 1936)); //必须显式传入类对象,需要临时对象
 
    std::cout << "\nContents:\n";
    for (President const& president: elections) {
        std::cout << president.name << " was elected president of "
                  << president.country << " in " << president.year << ".\n";
    }
    for (President const& president: reElections) {
        std::cout << president.name << " was re-elected president of "
                  << president.country << " in " << president.year << ".\n";
    }
}

输出为:

emplace_back:
I am being constructed.

push_back:
I am being constructed.
I am being moved.

Contents:
Nelson Mandela was elected president of South Africa in 1994.
Franklin Delano Roosevelt was re-elected president of the USA in 1936.

可以看到,push_back调用了两个构造函数


再多添加几个元素:

#include <vector>
#include <string>
#include <iostream>
 
struct President
{
    std::string name;
    std::string country;
    int year;
 
    President(std::string p_name, std::string p_country, int p_year)
        : name(std::move(p_name)), country(std::move(p_country)), year(p_year)
    {
        std::cout << "I am being constructed.\n";
    }
    President(President&& other)
        : name(std::move(other.name)), country(std::move(other.country)), year(other.year)
    {
        std::cout << "I am being moved.\n";
    }
    President& operator=(const President& other) = default;
};
 
int main()
{
    std::vector<President> elections;
    std::cout << "emplace_back:\n";
    elections.emplace_back("Nelson Mandela", "South Africa", 1994);
    std::cout << std::endl;
    elections.emplace_back("Nelson Mandela", "South Africa", 1994);
    std::cout << std::endl;
    elections.emplace_back("Nelson Mandela", "South Africa", 1994);
    std::cout << std::endl;

 
    for (President const& president: elections) {
        std::cout << president.name << " was re-elected president of "
                  << president.country << " in " << president.year << ".\n";
    }
}

输出为:

emplace_back:
I am being constructed.

I am being constructed.
I am being moved.

I am being constructed.
I am being moved.
I am being moved.

Nelson Mandela was re-elected president of South Africa in 1994.
Nelson Mandela was re-elected president of South Africa in 1994.
Nelson Mandela was re-elected president of South Africa in 1994.

为什么也调用了多次拷贝构造呢?因为和原本vector中剩下元素的数量对应,猜测因为发生了整体的vector内存搬移

先进行内存reverse,再进行测试看看

#include <vector>
#include <string>
#include <iostream>
 
struct President
{
    std::string name;
    std::string country;
    int year;
 
    President(std::string p_name, std::string p_country, int p_year)
        : name(std::move(p_name)), country(std::move(p_country)), year(p_year)
    {
        std::cout << "I am being constructed.\n";
    }
    President(President&& other)
        : name(std::move(other.name)), country(std::move(other.country)), year(other.year)
    {
        std::cout << "I am being moved.\n";
    }
    President& operator=(const President& other) = default;
};
 
int main()
{
    std::vector<President> elections;
    
    elections.reserve(3);
    
    std::cout << "emplace_back:\n";
    elections.emplace_back("Nelson Mandela", "South Africa", 1994);
    std::cout << std::endl;
    elections.emplace_back("Nelson Mandela", "South Africa", 1994);
    std::cout << std::endl;
    elections.emplace_back("Nelson Mandela", "South Africa", 1994);
    std::cout << std::endl;

 
    for (President const& president: elections) {
        std::cout << president.name << " was re-elected president of "
                  << president.country << " in " << president.year << ".\n";
    }
}

则可看到输出只调用了普通构造函数

emplace_back:
I am being constructed.

I am being constructed.

I am being constructed.

Nelson Mandela was re-elected president of South Africa in 1994.
Nelson Mandela was re-elected president of South Africa in 1994.
Nelson Mandela was re-elected president of South Africa in 1994.

结论:

  • 尽量使用reserve先请求好够用的空间,对性能的提升很重要
  • cppreference给出的代码都很有代表性
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值