从源代码看push_back与emplace_back

从源代码看push_back与emplace_back

为了更好的阅读体验,可以跳转个人博客-从源代码看push_back与emplace_back,享受无打扰,无广告阅读。

引入

最近在复习C++,正好看到 push_backemplace_back,网上的教程各有说辞,令人困惑。为了更好地理解这两者的区别,我决定自己动手看看源代码,研究研究。

首先,让我们看一个常见的观点:

vector 的函数 emplace_back()
它和 push_back() 函数一样,都是用来在容器尾部插入一个元素。
区别在于,使用 push_back() 需要调用拷贝构造函数,而 emplace_back() 则是原地构造元素,不会触发拷贝构造,因此效率更高。

我们暂且不讨论这个观点的准确性,直接来看看以下的代码示例:

std::vector<A> v;
v.reserve(10);

A tem = A(1);  
// A Construction2
A tem3 = A(1); 
// A Construction2

v.emplace_back(1); 
// A Construction2

v.push_back(1); 
// A Construction2
// A Move Construction

v.emplace_back(tem); 
// A Copy Construction

v.push_back(tem); 
// A Copy Construction

v.emplace_back(A(1));
// A Construction2
// A Move Construction

v.push_back(A(1));
// A Construction2
// A Move Construction

v.push_back(std::move(A(1))); 
// A Construction2
// A Move Construction

v.emplace_back(std::move(A(1))); 
// A Construction2
// A Move Construction

v.push_back(std::move(tem3)); 
// A Move Construction

v.emplace_back(std::move(tem)); 
// A Move Construction

从代码中我们可以明显看出,只有在直接传入参数构造时,push_backemplace_back 的行为存在差异。
有小伙伴可能会疑惑,以下部分似乎有些不妥:

v.emplace_back(1); 
// A Construction2

v.push_back(1);  // 传入1?不应该传入A类对象吗?
// A Construction2
// A Move Construction

由此可见:

  • 两者之间的区别仅在于直接构造时的使用参数传递。
  • 而由于 push_back 只能接受对象,若忽略多余的构造过程,两者实际上没有本质区别。

此过程指的是 v.push_back(1);1 构造为 A 类对象。

因此,可以得出结论:这两者在本质上并没有太大的差异,在程序员视角中,甚至可以认为没有差距

源代码分析

1. push_back(const value_type& __x)

这是 push_back 的左值引用版本,用于向 std::vector 添加元素,具体步骤如下:

push_back(const value_type& __x) {
    if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) {
        _GLIBCXX_ASAN_ANNOTATE_GROW(1);
        _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x);
        ++this->_M_impl._M_finish;
        _GLIBCXX_ASAN_ANNOTATE_GREW(1);
    } else {
        _M_realloc_insert(end(), __x);
    }
}
解析:
  • 检查容量

    if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
    
    • _M_finish 指向当前 vector 的最后一个元素的下一个位置。
    • _M_end_of_storagevector 当前分配的内存的结束位置。
    • _M_finish 不等于 _M_end_of_storage 时,说明还有空间可以添加新元素。
  • 构造元素

    _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x);
    
    • 使用分配器构造一个新元素,内容与 __x 相同。
    • 这实际上是调用 __x 的拷贝构造函数。
  • 更新状态

    ++this->_M_impl._M_finish;
    
    • 增加 _M_finish 的值,以反映新元素的添加。
  • 处理异常

    • _GLIBCXX_ASAN_ANNOTATE_GROW(1)_GLIBCXX_ASAN_ANNOTATE_GREW(1) 用于 AddressSanitizer 的注释,帮助检测内存使用问题。
  • 重新分配内存

    else
    _M_realloc_insert(end(), __x);
    
    • 如果没有足够的空间,调用 _M_realloc_insert 来重新分配内存并插入元素 __x
省流:

若传入的是左值,则在Vector中调用拷贝构造函数,构造新对象。

2. push_back(value_type&& __x)

这是 push_back 的右值引用版本,适用于移动语义。它的实现非常简洁,实际上调用了 emplace_back

void push_back(value_type&& __x) {
    emplace_back(std::move(__x));
}
解析:
  • 移动构造
    • std::move(__x)__x 转换为右值,使其可以被移动。
    • 这个版本会直接调用 emplace_back,实现更高效的元素添加。
省流:

如果传入的是右值push_back 会调用emplace_back来构造新对象。

3. emplace_back(_Args&&... __args)

这是一个可变参数模板函数,用于在 vector 中就地构造新元素。

template<typename... _Args>
void emplace_back(_Args&&... __args) {
    if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) {
        _GLIBCXX_ASAN_ANNOTATE_GROW(1);
        _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, std::forward<_Args>(__args)...);
        ++this->_M_impl._M_finish;
        _GLIBCXX_ASAN_ANNOTATE_GREW(1);
    } else {
        _M_realloc_insert(end(), std::forward<_Args>(__args)...);
    }
}
解析:
  • 检查容量

    • push_back 类似,首先检查当前 vector 是否有足够的空间。
  • 就地构造元素

    _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, std::forward<_Args>(__args)...);
    
    • 使用 std::forward 完美转发参数,使得可以根据参数的类型(左值或右值)调用适当的构造函数。
  • 更新状态

    • 增加 _M_finish 的值,反映新元素的添加。
  • 重新分配内存

    • 如果没有足够的空间,同样调用 _M_realloc_insert
省流:

触发完美转发

  • 传入普通参数时调用普通构造函数。
  • 传入左值引用时调用拷贝构造函数。
  • 传入右值引用时调用移动构造函数。

总结

  1. push_back 的左值引用版本会复制元素并添加到 vector 中,如果没有空间,则重新分配内存,触发拷贝构造。
  2. push_back 的右值引用版本通过移动语义来提高效率,直接调用 emplace_back
  3. emplace_back 允许在 vector 中直接构造元素,使用完美转发来支持多种参数类型,进一步优化性能。

所以遇到问题可以去翻阅源代码,能够更加清晰的了解这些功能。

  • 12
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
vector的push_back和emplace_back都是向容器中添加元素的方法。它们的作用是相同的,都可以将元素添加到vector的末尾。 区别在于使用push_back时,需要传入一个已经存在的元素或临时创建的元素,而使用emplace_back时,可以直接传入构造该元素所需的参数。也就是说,emplace_back可以避免额外的复制和移动操作,因为它可以在容器内直接构造元素。 因此,如果要向vector中添加已经存在的元素或临时创建的元素,可以使用push_back;如果要向vector中添加新元素并避免额外的复制和移动操作,可以使用emplace_back。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [说说vector的emplace_back和push_back](https://blog.csdn.net/iaibeyond/article/details/119153008)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [C++ 中”emplace_back” 与 “push_back” 的区别](https://download.csdn.net/download/weixin_38548704/13997185)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云璃丶夢紡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值