emplace_back与push_back使用区别,move语义、与vector内存空间开辟弊端


一、emplace_back与push_back原理区别

当我们传递一个对象的参数给这两个函数时,分别由如下区别:
emplace_back支持直接将构造函数所需的参数传递过去,然后构建一个新的对象出来,然后填充到容器尾部的。
push_back首先会利用传入的参数调用构造函数构造这个临时对象,然后需要调用拷贝构造函数将这个临时对象放入容器中。原来的临时变量释放。这样会造成效率低下。

代码如下(示例):

#include <vector>
#include <string>
#include <iostream>
using namespace std;

class test
{
 public:
    test(int num): num(num){
        cout << "i am default construct!" << endl;
    }
    test(const test& t) {
        cout << "i am copy construct!" << endl;
    }
    ~test(){
        cout << "i am ~ construct!" << endl;
    }
    int num;
};
 
int main()
{
    vector<test> ivec;
    ivec.reserve(4);
    cout << endl << "ivec.push_back(1): ";
    ivec.push_back(1);
    cout << endl << "ivec.emplace_back(1): ";
    ivec.emplace_back(2);
    cout << endl << "end!!!!!!" << endl;
    return 0;
}

上面的输出结果:
在这里插入图片描述
可见,确实是push_back,会有:调用默认构造函数构建临时变量—》调用拷贝构造函数—》调用析构函数;
而emplace_back只有一个默认构造过程。

二、push构造时不支持无参数情况,emplace支持

代码如下(示例):

#include <vector>
#include <string>
#include <iostream>
using namespace std;

class test
{
 public:
     test(): num(0){
        cout << "i am default construct!" << endl;
    }
    test(int num): num(num){
        cout << "i am default construct!" << endl;
    }
    test(const test& t) {
        cout << "i am copy construct!" << endl;
    }
    test(const test&& t): num(t.num) {
        cout << "i am move construct!" << endl;
    }
    ~test(){
        cout << "i am ~ construct!" << endl;
    }
    int num;
};

test gettest() {
    test t(11000);
    return t;
}
 
int main()
{
    vector<test> ivec;

    cout << endl << "ivec.emplace_back(): ";
    ivec.emplace_back();
    // 如果编译通过时,这里输出0。c++11里基本类型变量没有初始化时,默认初始化为0
    cout << ivec.back().num << endl;
    // 到这一步编译报错 
    cout << endl << "ivec.emplace_back(): ";
    ivec.push_back();

    cout << endl << "end!!!!!!" << endl;

    return 0;
}

编译报错:
在这里插入图片描述

三、move语义

move语义的用法陆陆续续看了好多文章,还是一知半解T…T,等以后再好好学习,这里主要是emplace也用到了move的东西。可以参考下文章:https://blog.youkuaiyun.com/p942005405/article/details/84644069
写个代码记录下。如下:

#include <vector>
#include <string>
#include <iostream>
using namespace std;

class test
{
 public:
     test(): num(0){
        cout << "i am default construct!" << endl;
    }
    test(int num): num(num){
        cout << "i am default construct!" << endl;
    }
    test(const test& t) {
        cout << "i am copy construct!" << endl;
    }
    test(const test&& t): num(t.num) {
        cout << "i am move construct!" << endl;
    }
    ~test(){
        cout << "i am ~ construct!" << endl;
    }
    int num;
};

test gettest() {
    test t(11000);
    return t;
}
 
int main()
{
    vector<test> ivec;
    ivec.reserve(8);

    cout << endl << "ivec.emplace_back(gettest()): ";
    // 或者ivec.emplace_back(test(3)); 一样达到返回一个临时变量的效果,临时变量是右值
    ivec.emplace_back(gettest());
    cout << ivec.back().num << endl;

    test t(4);
    cout << endl << "ivec.emplace_back(t): ";
    ivec.emplace_back(t);

    cout << endl << "ivec.emplace_back(move(t)): ";
    ivec.emplace_back(move(t));

    cout << t.num << endl;

    cout << endl << "end!!!!!!" << endl;

    return 0;
}

在这里插入图片描述
move语义待续。。。

四、 vector内存开辟原理

为了避免vector空间不够开辟新内存进行元素拷贝是的干扰,添加语句 ivec.reserve(4) 是有必要的。
在开始代码测试的时候,一直会出现多了一个拷贝构造函数的过程,原来是vector在没有初始化时,容量为0,当push元素进去但内存不够时,vector按照当前两倍的大小开辟新的内存块,因此就需要将就内存块的元素拷贝到新的内存块,同时销毁旧内存上的元素。见如下示例:

#include <vector>
#include <string>
#include <iostream>
using namespace std;

class test
{
 public:
    test(int num): num(num){
        cout << "i am default construct!" << endl;
    }
    test(const test& t) {
        cout << "i am copy construct!" << endl;
    }
    ~test(){
        cout << "i am ~ construct!" << endl;
    }
    int num;
};
 
int main()
{
    vector<test> ivec;
    // ivec.reserve(4);
    cout << "capacity:" << ivec.capacity() << endl;
    cout << endl << "ivec.push_back(1): ";
    ivec.push_back(1);
    cout << "capacity:" << ivec.capacity() << endl;
    cout << endl << "ivec.emplace_back(1): ";
    ivec.emplace_back(2);
    cout << endl << "ivec.emplace_back(1): ";
    ivec.emplace_back(2);
    cout << "capacity:" << ivec.capacity() << endl;
    cout << endl << "end!!!!!!" << endl;

    return 0;
}

输出结果:
在这里插入图片描述
可见,每次当vector预留的内存空间不够,开辟内存后,对之前的每个元素(不包含当前元素)生成一份拷贝,再把原来的析构,当元素量大的时候,会造成相当大的效率问题!!!!


参考文章:
https://zhuanlan.zhihu.com/p/213853588
https://blog.youkuaiyun.com/ShenHang_/article/details/120267857
https://blog.youkuaiyun.com/p942005405/article/details/84764104
https://blog.youkuaiyun.com/p942005405/article/details/84644069

PS: 这算是自己第一次认真且相对完整的写一篇博客,直观感觉就是:通过查询资料和自己编程实现与验证自己的想法时,很有收获但真不容易,这文章前前后后花了肯定有六七个小时!!!!纪念一下,希望以后保持习惯和加快速度!!!!

<think>在C++标准库容器(如std::vector)中,`emplace_back``push_back`都用于向容器尾部添加元素,但它们在实现方式效率上有重要区别:###1.`push_back`-添加元素时,先构造一个临时对象,然后通过移动或拷贝构造函数将临时对象添加到容器中。-对于非平凡类型(非内置类型),这可能导致额外的构造析构开销。-示例:```cppstd::vector<std::string>vec;vec.push_back("Hello");//创建临时string对象,然后移动(或拷贝)到vector中```###2.`emplace_back`-直接在容器内存中构造对象,使用提供的参数调用对象的构造函数。-避免了临时对象的创建,因此效率更高(特别是对于非移动构造的类型)。-示例:```cppstd::vector<std::string>vec;vec.emplace_back("Hello");//直接在vector中构造string,避免临时对象```###关键区别:|特性|`push_back`|`emplace_back`||------------------|-------------|----------------||构造方式|拷贝/移动|直接构造||临时对象|可能产生|不产生||参数传递|接受对象|接受构造参数||性能(非平凡类型)|较低|较高|###代码示例比较:```cpp#include<iostream>#include<vector>classMyClass{public:MyClass(intx,doubley){std::cout<< "Constructor\n";}MyClass(constMyClass&){std::cout<< "CopyConstructor\n";}MyClass(MyClass&&)noexcept{std::cout<< "MoveConstructor\n";}};intmain(){std::vector<MyClass>vec;std::cout<< "push_back:\n";vec.push_back(MyClass(1,2.5));//先构造临时对象,再移动(或拷贝)std::cout<< "\nemplace_back:\n";vec.emplace_back(1,2.5);//直接构造,无临时对象}```输出结果:```push_back:ConstructorMoveConstructor//或者拷贝构造(如果没有移动构造函数)emplace_back:Constructor//只有一次构造```###使用建议:1.对于内置类型(如`int`、`double`)或移动成本低的类型,两者性能差异很小。2.对于复杂对象(尤其是构造成本高或移动成本高的类型),优先使用`emplace_back`。3.注意:`emplace_back`可能引发异常安全问题(构造函数异常时容器状态不变,但需要确保类型正确)。###注意事项:-`emplace_back`需要C++11或更新版本。-避免错误传递参数导致意外构造(如传递初始化列表需用花括号`{}`)。-在需要显式转换时,`push_back`有时更明确(如`push_back({1,2})`)。---§§相关问题§§:1.如何正确使用`emplace_back`构造包含多个参数的对象?2.为什么`emplace_back`可能比`push_back`更高效?3.在哪些情况下应该优先使用`push_back`而不是`emplace_back`?4.`emplace_back`在异常安全方面有什么特别之处?</think> 在C++容器(如`std::vector`)中,`emplace_back` `push_back` 都用于添加元素,但实现方式效率有重要区别: ### 关键区别总结 | 特性 | `push_back` | `emplace_back` | |---------------------|---------------------------------|-----------------------------------| | **参数** | 接收对象(需构造/移动) | 接收构造参数(直接构造) | | **操作过程** | 拷贝构造/移动构造 | 原地构造(直接在容器内存创建) | | **性能(复杂对象)**| 可能产生临时对象 | 避免临时对象移动/拷贝操作 | | **C++版本** | C++98 起支持 | C++11 起支持 | ### 详细解析代码示例 ```cpp #include <vector> #include <iostream> class ComplexObject { public: int id; std::string name; // 构造函数 ComplexObject(int id, const std::string& name) : id(id), name(name) { std::cout << "构造对象 #" << id << "\n"; } // 拷贝构造函数 ComplexObject(const ComplexObject& other) : id(other.id), name(other.name) { std::cout << "拷贝构造 #" << id << "\n"; } // 移动构造函数 ComplexObject(ComplexObject&& other) noexcept : id(std::move(other.id)), name(std::move(other.name)) { std::cout << "移动构造 #" << id << "\n"; } }; int main() { std::vector<ComplexObject> vec; // 使用 push_back - 需要创建临时对象 std::cout << "--- push_back ---\n"; vec.push_back(ComplexObject(1, "push_temp")); /* 输出: 构造对象 #1 (临时对象) 移动构造 #1 (移动到容器) */ // 使用 emplace_back - 直接构造 std::cout << "\n--- emplace_back ---\n"; vec.emplace_back(2, "emplace_direct"); /* 输出: 构造对象 #2 (直接在容器内存构造) */ } ``` ### 何时选择哪种方法 1. **推荐`emplace_back`**: - 构造参数复杂时(如多个参数) - 对象不支持高效移动操作时 - 需要优化性能的关键路径代码 2. **推荐`push_back`**: - 添加已存在的对象(直接使用对象实例) - C++11 之前的代码兼容 - 需要显式类型转换时更安全 ### 特殊注意事项 1. 对于基础类型(`int`, `double`等)性能差异可忽略 2. 使用`emplace_back`时需保证参数能正确匹配构造函数 ```cpp std::vector<std::string> vec; vec.emplace_back(5, 'a'); // 直接构造:string(5, 'a') ``` 3. 注意初始化列表的特殊处理: ```cpp vec.push_back({1, "init"}); // 正确 // vec.emplace_back({1, "init"}); // 错误!需要明确参数类型 ``` ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值