测试代码

使用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. ChatGPT is at capacity right now.ChatGPT Plus subscriber login Add your email for a personalized login link的解决办法

    原因 你的ip地区用的人过多. 解决办法 在右侧页面填入邮箱,能收到邮件立刻就能登录,如果没有,就换地区试试,不行就等等吧 邮件示例:

  2. 一台服务器部署ShareWAF,后面接多台Web服务器,该如何配置?

    ShareWAF做为WAF,可以不只是WAF,还可以充当负载或路由的角色. 比如可以有这样一种部署架构: 在此结构中,ShareWAF部署于一台服务器,后面接多台独立的WEB服务器. ShareWAF ...

  3. MyCat2 分表分库

    1.添加数据库.存储数据源 我们在读写分离那边已经生成过,不需要在执行,如果没有执行过,执行下面注解,我们这边重新创建一个数据库db1 /*+ mycat:createDatasource{ &quo ...

  4. Python: 取消numpy科学计数法

    Numpy中默认是使用科学计数法来显示数据的,但是这种做法往往不利于我们观测数据,比如坐标数据等.那么如何取消numpy科学计数法呢,请往下看. np.set_printoptions() impor ...

  5. 设置点击事件时Unable to preventDefault inside passive event listener due to target being treated as passive

    问题 当使用fastClick.js设置点击事件时,控制台报错: [Intervention] Unable to preventDefault inside passive event listen ...

  6. datax clickhousewriter插件下载 elasticsearchwriter插件下载

    有用点手推荐!!! clickhousewriter: 链接:https://pan.baidu.com/s/1_dx7Y1P31yScNQvz0UH2WA 提取码:cwvv elasticsearc ...

  7. Python 自动化中三种等待时间的详解

    1.强制等待 强制等待是最简单的一种等待方式,强制让浏览器等待X秒,不管当前操作是否完成,是否可以进行下一步操作,都必须等X秒的时间. 使用方法:time.sleep(X) .在python中是基于t ...

  8. [746] Interlude Update 3

    [746] Interlude Update 3 Client 00 SendProtocolVersion 01 MoveBackwardToLocation 02 Say 03 RequestEn ...

  9. C# 前台线程 后台线程区别

    前台线程 会随进程一起结束 不管是否完成,后台线程需要执行完毕,进程才能结束 例子: class Program { static void Main(string[] args) { Thread ...

  10. SQL数学函数学习

    笔记来自如鹏网杨中科老师所著的 <程序员的SQL金典>#创建数据库表 CREATE Table T_Person ( FIdNumber VARCHAR(20), FName varcha ...