C++语法|emplace_back vs.push_back|他们的效率真的有区别吗?|最后讨论一个突发问题

STL版本如下:
Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.

分析__construct_one_at_end()

我们首先分析该函数:

  void __construct_one_at_end(_Args&& ...__args) {
    _ConstructTransaction __tx(*this, 1);
    __alloc_traits::construct(this->__alloc(), _VSTD::__to_address(__tx.__pos_),
        _VSTD::forward<_Args>(__args)...);
    ++__tx.__pos_;
  }

用于在 vector 容器的末尾构造一个新的元素。该函数利用完美转发(perfect forwarding),将参数直接传递给元素的构造函数,从而在容器的存储空间中直接构造对象。
具体细节感兴趣可以自行查看

emplace_back()和push_back()

对于emplace_back我们只分析其核心代码:

template <class ..._Args>
vector<_Tp, _Allocator>::emplace_back(_Args&&... __args)
{
    if (this->__end_ < this->__end_cap())
    {
        __construct_one_at_end(_VSTD::forward<_Args>(__args)...);
    }
    else
        __emplace_back_slow_path(_VSTD::forward<_Args>(__args)...);
#if _LIBCPP_STD_VER > 14
    return this->back();
#endif
}

关于这段代码,我在【对象优化(三)|move移动语义和forward类型完美转发】的【使用forword类型完美转发】部分已经实现的push_back即通过该形式进行实现。

template <class _Tp, class _Allocator /* = allocator<_Tp> */>
class _LIBCPP_TEMPLATE_VIS vector {
    typedef _Tp                                             value_type;
    typedef value_type&                                     reference;
    typedef const value_type&                               const_reference;


	vector<_Tp, _Allocator>::push_back(const_reference __x)
	{
    	if (this->__end_ != this->__end_cap())
    	{
        	__construct_one_at_end(__x);
    	}
    	else
        	__push_back_slow_path(__x);
	}


	vector<_Tp, _Allocator>::push_back(value_type&& __x)
	{
    	if (this->__end_ < this->__end_cap())
    	{
        	__construct_one_at_end(_VSTD::move(__x));
    	}
    	else
        	__push_back_slow_path(_VSTD::move(__x));
	}

这两个函数其实仍然能实现emplace的主要功能,唯一的区别就是传入参数,那么现在我们一起来分析一下传参的不同

分析三个函数的形参列表

1. 对于 push_back(const_reference __x)
这个版本的 push_back 接收一个类型为 const _Tp& 的常量引用参数。
2. 对于push_back(value_type&& __x)
这个版本的 push_back 接收一个右值引用参数 _Tp&&。
3. 对于emplace_back(_Args&&... __args)
可以接受任意类型的参数,通过模版类型推导和类型的完美转发实现插入

如果你看过这篇文章:【对象优化(三)|move移动语义和forward类型完美转发】

他们在效率上是绝对没区别的,因为底层的实现不就是定位new和类型完美转发吗?只不过是实现的方法不一样,但是结果绝对都是一样的

那么唯一的区别就在传参上了,emplace_back是可变参,push_back只能传入一个参数。

所以综上所述,直接给出一个结论吧。(我也不知道为什么很多人都这样做结论)

  • 当你要直接传入构造类对象的参数列表时,请使用emplace_back
  • 如果你已经有了类对象的实例,或者传入临时对象,请使用push_back

文章分享:
David Stone的回答,这篇文章中简单讨论了他俩真正的区别在于:隐式构造函数和显式构造函数。更具体的可以移步文章

重大疑问

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

class CMyString {
public:
    CMyString(const char* str = nullptr) {
        cout << "CMyString(const char*)" << endl;
        if (str != nullptr) {
            mptr = new char[strlen(str) + 1];
            strcpy(mptr, str);
        } else {
            mptr = new char[1];
            *mptr = '\0';
        }
    }

    ~CMyString() {
        cout << "~CMyString()" << endl;
        delete[] mptr;
        mptr = nullptr;
    }

    //带左值引用参数的拷贝构造
    CMyString(const CMyString& str) {
        cout << "CMyString(const CMyString&)" << endl;
        mptr = new char[strlen(str.mptr) + 1];
        strcpy(mptr, str.mptr);
    }
    //带右值引用参数的拷贝构造
    CMyString(CMyString &&str) {    //str引用的就是一个右值
        cout << "CMyString(CMyString &&str)" << endl;
        mptr = str.mptr;
        str.mptr = nullptr;
    }

    //带左值引用参数的赋值重载函数
    CMyString& operator=(const CMyString& str) {
        cout << "operator=(const CMyString&)" << endl;
        if (this == &str) {
            return *this;
        }
        delete[] mptr;
        mptr = new char[strlen(str.mptr) + 1];
        strcpy(mptr, str.mptr);
        return *this;
    }

    //带右值引用参数的赋值重载函数
    CMyString& operator=(CMyString &&str) {
        cout << "operator=(CMyString&&)" << endl;
        if (this == &str)
            return *this;

        delete[] mptr;
        
        mptr = str.mptr;
        str.mptr = nullptr;
        return *this;
    }

    const char* c_str() const { return mptr; }
private:
    char* mptr;

    friend CMyString operator+(const CMyString &lhs,
                    const CMyString &rhs);
    friend ostream& operator<<(ostream &out, const CMyString &str);
};

CMyString operator+(const CMyString &lhs,
                    const CMyString &rhs){
    //char *ptmp = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];
    CMyString tempStr;
    tempStr.mptr = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];
    strcpy(tempStr.mptr, lhs.mptr);
    strcat(tempStr.mptr, rhs.mptr);
    //delete []ptmp;
    return tempStr;
    //return CMyString(ptmp);
}

ostream& operator<<(ostream &out, const CMyString &str) {
    out << str.mptr;
    return out;
}

我实现了MyString类,并做了如下三个实验,这是为什么呢?

实验一

实验二

实验三

这是什么情况??为什么emplace_back效率会低这么多??发生了什么??

  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值