测试代码

使用emplace_back可以避免不必要的构造和拷贝,而是直接在向量的内存位置执行construct进行构造,代码看起来也更加简洁。

但是在使用的时候,会发现有一些和直观不太对应的情况。例如,在下面的例子中,使用new的时候,只有花括号可以通过编译,而同样的emplace_back就不行。

tsecer@harry$ cat -n default.new.agg.init.cpp
1 #include <vector>
2
3 struct tsecer
4 {
5 int x;
6 int y;
7 };
8
9 int harry()
10 {
11 new tsecer{1,2};
12 new tsecer(1,2);
13
14 std::vector<tsecer> va;
15 va.emplace_back(1, 2);
16 va.emplace_back({1, 2});
17 }
18
tsecer@harry$ gcc -c default.new.agg.init.cpp
default.new.agg.init.cpp: In function 'int harry()':
default.new.agg.init.cpp:12:19: error: new initializer expression list treated as compound expression [-fpermissive]
new tsecer(1,2);
^
default.new.agg.init.cpp:12:19: error: no matching function for call to 'tsecer::tsecer(int)'
default.new.agg.init.cpp:3:8: note: candidate: tsecer::tsecer()
struct tsecer
^~~~~~
default.new.agg.init.cpp:3:8: note: candidate expects 0 arguments, 1 provided
default.new.agg.init.cpp:3:8: note: candidate: constexpr tsecer::tsecer(const tsecer&)
default.new.agg.init.cpp:3:8: note: no known conversion for argument 1 from 'int' to 'const tsecer&'
default.new.agg.init.cpp:3:8: note: candidate: constexpr tsecer::tsecer(tsecer&&)
default.new.agg.init.cpp:3:8: note: no known conversion for argument 1 from 'int' to 'tsecer&&'
default.new.agg.init.cpp:16:27: error: no matching function for call to 'std::vector<tsecer>::emplace_back(<brace-enclosed initializer list>)'
va.emplace_back({1, 2});
^
In file included from /usr/include/c++/7/vector:69:0,
from default.new.agg.init.cpp:1:
/usr/include/c++/7/bits/vector.tcc:95:7: note: candidate: void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {}; _Tp = tsecer; _Alloc = std::allocator<tsecer>]
vector<_Tp, _Alloc>::
^~~~~~~~~~~~~~~~~~~
/usr/include/c++/7/bits/vector.tcc:95:7: note: candidate expects 0 arguments, 1 provided
In file included from /usr/include/c++/7/x86_64-redhat-linux/bits/c++allocator.h:33:0,
from /usr/include/c++/7/bits/allocator.h:46,
from /usr/include/c++/7/vector:61,
from default.new.agg.init.cpp:1:
/usr/include/c++/7/ext/new_allocator.h: In instantiation of 'void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = tsecer; _Args = {int, int}; _Tp = tsecer]':
/usr/include/c++/7/bits/alloc_traits.h:475:4: required from 'static void std::allocator_traits<std::allocator<_Tp1> >::construct(std::allocator_traits<std::allocator<_Tp1> >::allocator_type&, _Up*, _Args&& ...) [with _Up = tsecer; _Args = {int, int}; _Tp = tsecer; std::allocator_traits<std::allocator<_Tp1> >::allocator_type = std::allocator<tsecer>]'
/usr/include/c++/7/bits/vector.tcc:100:30: required from 'void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {int, int}; _Tp = tsecer; _Alloc = std::allocator<tsecer>]'
default.new.agg.init.cpp:15:25: required from here
/usr/include/c++/7/ext/new_allocator.h:136:4: error: new initializer expression list treated as compound expression [-fpermissive]
{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/7/ext/new_allocator.h:136:4: error: no matching function for call to 'tsecer::tsecer(int)'
default.new.agg.init.cpp:3:8: note: candidate: tsecer::tsecer()
struct tsecer
^~~~~~
default.new.agg.init.cpp:3:8: note: candidate expects 0 arguments, 1 provided
default.new.agg.init.cpp:3:8: note: candidate: constexpr tsecer::tsecer(const tsecer&)
default.new.agg.init.cpp:3:8: note: no known conversion for argument 1 from 'int' to 'const tsecer&'
default.new.agg.init.cpp:3:8: note: candidate: constexpr tsecer::tsecer(tsecer&&)
default.new.agg.init.cpp:3:8: note: no known conversion for argument 1 from 'int' to 'tsecer&&'
tsecer@harry$

聚合类型

C++定义了aggregate类型,从这个说明来看,感觉大致可以理解为传统的C语言中的上struct结构:没有构造函数,没有virtual函数和virtual继承。

它们的初始化也和C中的结构初始化相同,也是通过花括号初始化。

An aggregate is one of the following types:
array type
class type (typically, struct or union), that has
no private or protected direct (since C++17)non-static data members
no user-declared constructors(until C++11)
no user-provided, inherited, or explicit constructors(since C++11)(until C++20)
no user-declared or inherited constructors (since C++20)
no virtual, private, or protected (since C++17) base classes
no virtual member functions
no default member initializers(since C++11)(until C++14) The elements of an aggregate are:
for an array, the array elements in increasing subscript order, or
for a class, the non-static data members that are not anonymous bit-fields, in declaration order.(until C++17)
for a class, the direct base classes in declaration order, followed by the direct non-static data members that are neither anonymous bit-fields nor members of an anonymous union, in declaration order.

List-initialization

列表初始化感觉是针对C++初始化定义的语法,主要是为了让C++的初始化看起来更统一,所以这种初始化也被称为“uniform initialization”。

这种定义可以看到,它是在特定(常用)位置可以使用花括号来表示初始化需要的参数。这里说“特定位置”的原因在于:如果花括号位于这些位置,它们才会作为初始化内容,因为它也可能是一个语句块。

Syntax
Direct-list-initialization
T object { arg1, arg2, ... }; (1)
T { arg1, arg2, ... } (2)
new T { arg1, arg2, ... } (3)
Class { T member { arg1, arg2, ... }; }; (4)
Class::Class() : member { arg1, arg2, ... } {... (5)
Copy-list-initialization
T object = { arg1, arg2, ... }; (6)
function ({ arg1, arg2, ... }) (7)
return { arg1, arg2, ... }; (8)
object [{ arg1, arg2, ... }] (9)
object = { arg1, arg2, ... } (10)
U ({ arg1, arg2, ... }) (11)
Class { T member = { arg1, arg2, ... }; }; (12)

以new后的括号为例

在解析new之后的初始化表达式时,对于花括号(CPP_OPEN_BRACE)号和圆括号的处理流程并不相同,流程的不同只是表面现象,这个不同跟意味着生成的语法树节点类型也不相同。

这里要注意的是,在花括号分支执行的

expression_list = make_tree_vector_single (t);

也就是虽然花括号内可能有多个元素,它是它们整体作为vector的一个元素(元素类型是init_list_type_node)。

/* Parse a new-initializer.

   new-initializer:
( expression-list [opt] )
braced-init-list Returns a representation of the expression-list. */ static vec<tree, va_gc> *
cp_parser_new_initializer (cp_parser* parser)
{
vec<tree, va_gc> *expression_list; if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE))
{
tree t;
bool expr_non_constant_p;
cp_lexer_set_source_position (parser->lexer);
maybe_warn_cpp0x (CPP0X_INITIALIZER_LISTS);
t = cp_parser_braced_list (parser, &expr_non_constant_p);
CONSTRUCTOR_IS_DIRECT_INIT (t) = 1;
expression_list = make_tree_vector_single (t);
}
else
expression_list = (cp_parser_parenthesized_expression_list
(parser, non_attr, /*cast_p=*/false,
/*allow_expansion_p=*/true,
/*non_constant_p=*/NULL)); return expression_list;
}

Parameter pack

emplace_back是通过Parameter pack来实现。对于模板的该机制来说,它只是承担了一个中间的打解包动作:在调用位置把列表打包,在用到的位置再解包。

也就是说,调用处的多个参数在使用的时候同样是展开为多个参数。

      vector<_Tp, _Alloc>::
emplace_back(_Args&&... __args)
{
if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
{
_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
std::forward<_Args>(__args)...);
++this->_M_impl._M_finish;
}
else
_M_realloc_insert(end(), std::forward<_Args>(__args)...);
#if __cplusplus > 201402L
return back();
#endif
}

gcc错误提示的位置

从gcc的代码看,如果一个类型type_build_ctor_call返回为false,则初始化只能有一个元素(就是前面提到的init_list_type_node)类型,而这个类型只会在特定位置被识别为这种类型。

/* Like build_x_compound_expr_from_list, but using a VEC.  */
tree
build_x_compound_expr_from_vec (vec<tree, va_gc> *vec, const char *msg,
tsubst_flags_t complain)
{
if (vec_safe_is_empty (vec))
return NULL_TREE;
else if (vec->length () == 1)
return (*vec)[0];
else
{
tree expr;
unsigned int ix;
tree t; if (msg != NULL)
{
if (complain & tf_error)
permerror (input_location,
"%s expression list treated as compound expression",
msg);
else
return error_mark_node;
} expr = (*vec)[0];
for (ix = 1; vec->iterate (ix, &t); ++ix)
expr = build_x_compound_expr (EXPR_LOCATION (t), expr,
t, complain); return expr;
}
} /* Nonzero if we need to build up a constructor call when initializing an
object of this class, either because it has a user-declared constructor
or because it doesn't have a default constructor (so we need to give an
error if no initializer is provided). Use TYPE_NEEDS_CONSTRUCTING when
what you care about is whether or not an object can be produced by a
constructor (e.g. so we don't set TREE_READONLY on const variables of
such type); use this function when what you care about is whether or not
to try to call a constructor to create an object. The latter case is
the former plus some cases of constructors that cannot be called. */ bool
type_build_ctor_call (tree t)
{
tree inner;
if (TYPE_NEEDS_CONSTRUCTING (t))
return true;
inner = strip_array_types (t);
if (!CLASS_TYPE_P (inner) || ANON_AGGR_TYPE_P (inner))
return false;
if (!TYPE_HAS_DEFAULT_CONSTRUCTOR (inner))
return true;
if (cxx_dialect < cxx11)
return false;
/* A user-declared constructor might be private, and a constructor might
be trivial but deleted. */
for (tree fns = lookup_fnfields_slot (inner, complete_ctor_identifier);
fns; fns = OVL_NEXT (fns))
{
tree fn = OVL_CURRENT (fns);
if (!DECL_ARTIFICIAL (fn)
|| DECL_DELETED_FN (fn))
return true;
}
return false;
}

字面list initialization的模板推导

在模板类型推导过程中,init_list_type_node类型节点和unknown_type_node一样,都无法提供任何信息,也就是init_list_type_node本身没有类型信息,它需要依附/依赖于特定类型进行解析。

  if (arg == error_mark_node)
return unify_invalid (explain_p);
if (arg == unknown_type_node
|| arg == init_list_type_node)
/* We can't deduce anything from this, but we might get all the
template args from other function args. */
return unify_success (explain_p);
///...
}

从下面的例子中可以看到,字面的花括号在编译器内部是init_list_type_node类型节点,它没有语言用户可见的类型,例如int、float、struct等信息。

tsecer@harry$ cat init_list_type_node.cpp
#include <vector>
#include <stdio.h> struct tsecer
{
int x;
int y;
void show()
{
printf("x %d y %d\n", x, y);
}
}; int main()
{
new tsecer{1,2};
(new tsecer({1,2}))->show();
std::vector<tsecer> va;
va.emplace_back(({1, 2;}));
} tsecer@harry$ gcc -c init_list_type_node.cpp
In file included from /usr/include/c++/7/x86_64-redhat-linux/bits/c++allocator.h:33:0,
from /usr/include/c++/7/bits/allocator.h:46,
from /usr/include/c++/7/vector:61,
from init_list_type_node.cpp:1:
/usr/include/c++/7/ext/new_allocator.h: In instantiation of 'void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = tsecer; _Args = {int}; _Tp = tsecer]':
/usr/include/c++/7/bits/alloc_traits.h:475:4: required from 'static void std::allocator_traits<std::allocator<_Tp1> >::construct(std::allocator_traits<std::allocator<_Tp1> >::allocator_type&, _Up*, _Args&& ...) [with _Up = tsecer; _Args = {int}; _Tp = tsecer; std::allocator_traits<std::allocator<_Tp1> >::allocator_type = std::allocator<tsecer>]'
/usr/include/c++/7/bits/vector.tcc:100:30: required from 'void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {int}; _Tp = tsecer; _Alloc = std::allocator<tsecer>]'
init_list_type_node.cpp:19:30: required from here
/usr/include/c++/7/ext/new_allocator.h:136:4: error: no matching function for call to 'tsecer::tsecer(int)'
{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
init_list_type_node.cpp:4:8: note: candidate: tsecer::tsecer()
struct tsecer
^~~~~~
init_list_type_node.cpp:4:8: note: candidate expects 0 arguments, 1 provided
init_list_type_node.cpp:4:8: note: candidate: constexpr tsecer::tsecer(const tsecer&)
init_list_type_node.cpp:4:8: note: no known conversion for argument 1 from 'int' to 'const tsecer&'
init_list_type_node.cpp:4:8: note: candidate: constexpr tsecer::tsecer(tsecer&&)
init_list_type_node.cpp:4:8: note: no known conversion for argument 1 from 'int' to 'tsecer&&'
tsecer@harry$

结论

aggregate initialization作为对于简单类型的直接初始化使用比较方便,但是花括号必须和目标类型有明确的关联关系。例如,在new T后面,return 之后或者函数参数位置,因为这些位置它们对应的目标类型是可以简单的推导出来。这也是C++语言“强类型”的特点。

emplace_back类是依赖模板的parameter pack,该机制本质上是对参数列表的打解包:也就是在调用处和使用处一样,都是参数列表。

字面花括号作为编译器内部使用的init_list_type_node节点,并不是C++语言使用者可以作为推导的类型。

从网上资料看,C++20有可能会允许前面编译错误的代码,也就是可以允许va.emplace_back(1, 2);来初始化数组元素。

使用emplace_back的new initializer expression list treated as compound expression提示看聚合初始化和parameter pack的更多相关文章

  1. C# ORM中Dto Linq Expression 和 数据库Model Linq Expression之间的转换

    今天在百度知道中看到一个问题,研究了一会便回答了: http://zhidao.baidu.com/question/920461189016484459.html 如何使dto linq 表达式转换 ...

  2. Cannot use isset() on the result of an expression (you can use "null !== expression" instead)

    if (isset($array[2])){ 抛出错误  Cannot use isset() on the result of an expression (you can use "nu ...

  3. 如何获取Expression Design 4工具与Expression Blend 4工具

    在VS2010+C#+WPF 开发项目过程中涉及到界面的布局与设计,网上有人讲采用Expression Design 4与Expression Blend 4工具相当方便, 于是决定试看看,下面将这个 ...

  4. [C#] C# 知识回顾 - 表达式树 Expression Trees

    C# 知识回顾 - 表达式树 Expression Trees 目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译表达式树 执行表达式树 修改表达 ...

  5. Ternary Expression Parser

    Given a string representing arbitrarily nested ternary expressions, calculate the result of the expr ...

  6. .NET Core中合并Expression<Func<T,bool>>的正确姿势

    这是在昨天的 .NET Core 迁移中遇到的问题,之前在 .NET Framework 中是这样合并 Expression<Func<T,bool>> 的: public s ...

  7. Leetcode: Ternary Expression Parser

    Given a string representing arbitrarily nested ternary expressions, calculate the result of the expr ...

  8. Expression Tree Basics 表达式树原理

    variable point to code variable expression tree data structure lamda expression anonymous function 原 ...

  9. Spring学习总结(四)——表达式语言 Spring Expression Language

    SpEL简介与功能特性 Spring表达式语言(简称SpEL)是一个支持查询并在运行时操纵一个对象图的功能强大的表达式语言.SpEL语言的语法类似于统一EL,但提供了更多的功能,最主要的是显式方法调用 ...

  10. Reflection和Expression Tree解析泛型集合快速定制特殊格式的Json

    很多项目都会用到Json,而且大部分的Json都是格式固定,功能强大,转换简单等,标准的key,value集合字符串:直接JsonConvert.SerializeObject(List<T&g ...

随机推荐

  1. 下拉刷新 get请求 post请求 onLoad

       "enablePullDownRefresh": true 下拉刷新之后背景颜色  "backgroundColor": "#efefef&qu ...

  2. 配置jupyter lab遇到 libffi.so.7缺失

    问题描述 使用 jupyter lab password 命令 配置jupyter 密码 报错 思路 去换一个conda环境 找到其lib内部的 libffi.so.7文件 解决方法 ln -s 其他 ...

  3. Pygame的基本应用(14周)

    制作一个跳跃的小球游戏        创建一个游戏窗口,然后在窗口内创建一个小球.以一定的速度移动小球,当小球碰到游戏窗口的边缘时,小球弹回,继续移动.代码如下: import sysimport p ...

  4. sping入门介绍-bean标签的属性

    bean标签的属性 1 .基础属性 <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" ...

  5. python读取图片相关属性

    背景:工作中用到一些基础的图片处理的任务,比如获取图片宽高.获取图片的旋转角度等等图片属性,都是比较零散的,这里简单做个记录备忘 这里用到的库exifread,安装 pip isntall exifr ...

  6. 如何使用postman

    一. 了解postman 1. 什么是postman? ------ 软件测试用来做接口测试的工具. 2. 如何下载postman ------ https://www.getpostman.com/ ...

  7. jmeterGUI&非GUI模式之如何减负性能调优

    jmeter之如何减负-实现稳定超高并发测试(性能调优)在测试过程中,初学者使用工具不当,添加众多监控组件,非常想看到实时报告,跑不了一会,jmeter就卡死甚至内存耗尽,只得重启,之前的统计报告没了 ...

  8. Kubernetes 设置master相关

    设置master调度命令 1.设置master一般情况下不接受pod调度 sudo kubectl taint nodes master node-role.kubernetes.io/master= ...

  9. 使用.Net工具安装某种程序

    使用.Net开发的一个程序,安装时需要使用.net的工具. Emmm... 好长时间不用,有点忘了,偶尔翻到,记录一下 @echo off setlocal chcp 65001 set U_PATH ...

  10. JSP过滤器、Session监听器、Servlet控制器的关系和执行顺序

    1.首先配置好过滤器和监听器,访问index.jsp页面(在index.jsp中设置session的Attribute属性.session的失效时间,查看的顺序是什么?); 1.运行Tomact的结果 ...