免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 4018 | 回复: 5
打印 上一主题 下一主题

[C++] emplace_back似乎可以不用move就能实现 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2015-11-25 23:05 |只看该作者 |倒序浏览
stl容器的emplace_back比push_back而言,用move语义来移动一个元素,而不是拷贝一个元素,所以效率可以更高。我看gcc的源码,push_back(T&&)也是内部用emplace_back一个move过的元素

问题:
但是这样做是为了效率考虑的话,我感觉placement_new的效率更高啊,容器先reserve(),然后以reserve出来的空间作为地址,做new()来构造一个新的元素,这样一来连move都省掉了,岂不是更好。
感觉emplace_back需要依赖move语义,有点多次一举的意思啊

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
2 [报告]
发表于 2015-11-26 03:59 |只看该作者
回复 1# Ruckus优科

  1. void push_back(const value_type& x);
  2. void push_back(value_type&& x);
  3. template <class... Args> void emplace_back(Args&&... args);
复制代码
第1个是c++98的。
第2个是c++11的。 因为value_type&& x只能绑定右值, 所以可以将x用作move。
第3个也是c++11的。 Args&&不一定是右值引用, 这个东西叫universal references或forwarding references。
这个解释起来有些复杂。。。 还是看 Universal References in C++11—Scott Meyers 比较专业。。。

于是可以先从另一个(也许会更容易)的方式看待C++11新加入的这两货 —— push_back(&&)和emplace_back —— 的不同, 就是emplace_back的参数个数。。。

push_back的右值重载,传递的始终是一个参数, 类型是容器的元素类型。
vector<string> xs;
xs.push_back("implicit"); // 实际是调用/* non explict */ string(const char* s)构造一个临时的string对象
xs.push_back(string("explicit")); // 是显式调用 string(const char* s)构造。。。
xs.push_back(string("hello world", 5));  // 是调用 string (const char* s, size_t n) 构造。。。
xs.push_back(string(6, '0')); // 调用 string (size_t n, char c) 。。。
xs.push_back(string());  // 调用 string() 默认构造。。。
它们都是先用各种参数构造一个临时的string作为push_back的参数, 通过它构造出xs内的那个元素, 即xs.back()。 然后销毁那个临时的string。
语义上总共两个构造。 一个析构。 第2个构造是通过move。

而emplace_back是不定长,不定类型的函数模板。
xs.emplace_back("explicit"); // string(const char* s)
xs.emplace_back("hello world", 5); // string (const char* s, size_t n)
xs.emplace_back(6, '0'); // string (size_t n, char c)
xs.emplace_back(); // string()
这些参数(包括最后一个没有参数)转发到string的构造函数(包括默认构造函数), 直接构造xs.back()。
语义上总共一个构造。 零个析构。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
3 [报告]
发表于 2015-11-26 05:14 |只看该作者
Ruckus优科 发表于 2015-11-25 23:05
但是这样做是为了效率考虑的话,我感觉placement_new的效率更高啊,容器先reserve(),然后以reserve出来的空间作为地址,做new()来构造一个新的元素,这样一来连move都省掉了,岂不是更好。
感觉emplace_back需要依赖move语义,有点多次一举的意思啊


再看了一次问题,我好像误解你的问题了。。。
emplace_back确实是直接placement new, 直接构造, 无须move。 所以关键点还是在前面省略的Args&&。。。

它不是rvalue reference, 而是universal references。 是为了完美转发(perfect forwarding), 保持参数的lvalue/rvalue性质。
当是rvalue时, 就可以move, 而当是lvalue时, 就别move坏了。

假设X可以通过Y构造:
struct Y {};
struct X {
      explicit X(Y const&);
      explicit X(Y&&);
};

vector<X> xs;
xs.emplace_back(Y()); // Y()产生临时对象, 是右值, 调用X(Y&&)
Y y;
xs.emplace_back(y); // y是左值, 于是只能调用X(Y const&)
xs.emplace_back(move(y)); // move(y) cast到右值, 于是会调用X(Y&&)

可见emplace_back会维持参数的lvalue/rvalue的性质并转发 —— 传给emplace_back的是右值才调用X(Y&&), 否则就调用X(Y const&)。

除了使用universal references外, 实现perfect forwarding还有另一半工作要做。仅仅看声明不够。。。
以单参数为例:
template <typename T> void emplace_back1(T&& x)
{
      value_type* p = back_address(); // 这是新的末尾元素所在地址
      new(p) value_type(std::forward<T>(x)); // 以forward<T>(x)为参数placement new
}
forward是一个条件cast, 只有当确实以右值调用emplace_back1时, 它才会cast到右值引用, 否则它只会返回左值引用。
于是就可以调用合适的value_type。

emplace_back和emplace_back1类似, 都声明为universal references, 并将参数使用std::forward进行带条件的cast, 将lvalue或rvalue参数原样转发给X。
只是emplace_back是不定长参数。

具体解释。。。  可以看Effective Modern C++。。。
Item 1: Understand template type deduction. 会说明:
1. void push_back(value_type&&)是rvalue reference, 而template <typename T> void emplace_back1(T&& x)是universal references
2. xs.emplace_back1(Y()), T会推演为Y, 而xs.emplace_back1(y), T会推演为Y&。

Item 28: Understand reference collapsing. 详细介绍利用 reference collapsing 实现 std::forward。


时隔多年。。。 Scott Meyers 叔叔继续教C++信徒做人。。。

论坛徽章:
44
15-16赛季CBA联赛之浙江
日期:2021-10-11 02:03:59程序设计版块每日发帖之星
日期:2016-07-02 06:20:0015-16赛季CBA联赛之新疆
日期:2016-04-25 10:55:452016科比退役纪念章
日期:2016-04-23 00:51:2315-16赛季CBA联赛之山东
日期:2016-04-17 12:00:2815-16赛季CBA联赛之福建
日期:2016-04-12 15:21:2915-16赛季CBA联赛之辽宁
日期:2016-03-24 21:38:2715-16赛季CBA联赛之福建
日期:2016-03-18 12:13:4015-16赛季CBA联赛之佛山
日期:2016-02-05 00:55:2015-16赛季CBA联赛之佛山
日期:2016-02-04 21:11:3615-16赛季CBA联赛之天津
日期:2016-11-02 00:33:1215-16赛季CBA联赛之浙江
日期:2017-01-13 01:31:49
4 [报告]
发表于 2015-11-26 12:17 |只看该作者
emplace_back本来就没有copy也没有move,它就是直接placement new的。
说move什么的是指参数,比如你用一个临时string对象作为参数去emplace_back一个string容器,那这个临时string参数会move过去而不是copy过去,和用string&&构造string是一样的。

论坛徽章:
0
5 [报告]
发表于 2015-11-26 22:36 |只看该作者
OwnWaterloo 发表于 2015-11-26 05:14
再看了一次问题,我好像误解你的问题了。。。
emplace_back确实是直接placement new, 直接构造, 无须 ...


谢谢,不过我尝试了一下emplace_back多个元素,似乎不行啊:

  1. #include <vector>
  2. #include <deque>
  3. #include <forward_list>
  4. using namespace std;

  5. int main()
  6. {
  7.     vector<int> vi;
  8.     vi.emplace_back(3,4);
  9.     cout << "Hello world!" << endl;
  10.     return 0;
  11. }
复制代码
编译报错: ...gcc\4.8.1\include\c++\ext\new_allocator.h|120|error: new initializer expression list treated as compound expression [-fpermissive]

gcc抱的这个错误是什么含义?

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
6 [报告]
发表于 2015-11-26 23:57 |只看该作者
本帖最后由 lost_templar 于 2015-11-26 23:57 编辑

回复 5# Ruckus优科
  1. void emplace_back(Args&&... args);
复制代码
里边的 args... 是作为 vector<T> 中 T 的 constructor 的参数出现的,而 int 类型构建的时候只能接受 0 个或 1 个参数, 不是两个。

可以这样测试

  1. #include <vector>

  2. struct x
  3. {
  4.     template< typename ... Args >
  5.     x( Args&& ... ){}
  6. };



  7. void f()
  8. {
  9.     std::vector<x> vx;
  10.     vx.emplace_back( );
  11.     vx.emplace_back( vx );
  12.     vx.emplace_back( vx, vx );
  13.     vx.emplace_back( vx, vx, vx );
  14.     vx.emplace_back( vx, vx, vx, vx );
  15. }

复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP