测试代码

使用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. 回归分析 3.X 多元线性回归

    多元线性回归模型 参数估计 模型表示 我们先将模型 \[y_{i}=\beta_{0}+\beta_{1} x_{i 1}+\cdots+\beta_{p} x_{i k}+\epsilon_{i}, ...

  2. ES6的总结的一些数组、字符串方法

    1.数组的方法 unshift() 数组头部添加内容 push() 数组尾部添加内容 pop() 数组尾部删除内容 shift() 数组头部删除内容 sort() 数组排序 a-b 升序 b-a 降序 ...

  3. Cisco——ASA和winserver2016配置l2tp over ipsec连接

    Cisco--ASA和winserver2016配置l2tp over ipsec连接 L2tp over ipsec vpn配置 网络拓扑图: 配置vpn时要确保Winserver15能够ping通 ...

  4. CISCO--配置单臂路由+DHCP

    CISCO--配置单臂路由+DHCP 1.在交换机中创建vlan10和20. Switch(config)#vlan 10 Switch(config-vlan)#vlan 20 2.接口Fa0/1配 ...

  5. iOS组件化 pod命令创建私有库详解【引用其他私有库、oc、Swift混编】

    1.命令创建pod pod lib create pod的名字 2.根据指令依次填写信息 3.填写完成后会自动打开项目 .然后修改podspec文件即可 4.创建当前pod的git 仓库.将当前代码放 ...

  6. MariaDB 搭建主备及主主

    一.主备 可参考:MariaDB之GTID主从复制 二.主主

  7. C# 教你如何终止Task线程

    我们在多线程中通常使用一个bool IsExit类似的代码来控制是否线程的运行与终止,其实使用CancellationTokenSource来进行控制更为好用,下面我们将介绍CancellationT ...

  8. PyCharm如何实现控制台换行显示

    举个例子 我现在想要看输出结果的所有数据然后再控制台输出的信息如下: 本来输出的内容有很多,但由于只显示了一行,因此想要看全部的内容还需要拖拉滚动条,挺麻烦的,而且看着也不方便,怎么让控制台信息全都直 ...

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

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

  10. Docker私服(Registry)

    Docker Registry安装 #拉取镜像 docker pull registry #创建文件夹 mkdir -p /var/my_registry #启动容器 docker run -d -- ...