测试代码

使用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. containerd.service containerd-1.6.8-linux-amd64.tar.gz cni-plugins-linux-amd64-v1.1.1.tgz 标准文件下载

    配置K8S时 可能会用到#systemcd来管理containerd,这https://raw.githubusercontent.com/containerd/containerd/main/con ...

  2. [Spring] SpringBoot启动流程

    SpringBoot启动流程 实例化流程 推断应用类型,创建的是REACTIVE应用.SERVlET应用.NONE三种中的一种 使用SpringFactoreisLoader查找并加载classpat ...

  3. grafana嵌入iframe,去除菜单和上方工具条

    1.首先修改grafana的配置:etc/grafana/grafana.ini,修改下面这两个配置为true 2.由于项目使用了nginx,要启用https,需要修改下面这几个配置:(不需要启用ht ...

  4. Java面向对象之抽象类abstract

    抽象类abstract 普通类 普通类可以直接产生实例化对象,并且在普通类之中可以包含有构造方法,普通方法.static 方法.常量.变量的内容. 所有的普通方法都会有一个"{}" ...

  5. v-if和v-show最重要一点

    vif是新建与销毁标签 show是显示与隐藏标签 如果涉及到权限操作的话 必须使用vif 因为vshow中把标签改成display:none的话可以手动改成显示

  6. Unity 使用IO流获取PNG/JPG/GIF/BMP的宽高【转】

    原文链接:https://blog.csdn.net/flj135792468/article/details/107963280?utm_medium=distribute.pc_relevant. ...

  7. (mysql笔记)GROUP_CONCAT() 把多行数据合并

    不合并查询: 合并查询: SELECT GROUP_CONCAT(id) FROM orderinfo WHERE enterpriseid = 2265 AND shopid =0 AND orde ...

  8. ROS自动检测安装功能依赖包

    cd ~/ros_ws/src sudo rosdepc init & rosdepc update cd .. rosdepc install -i --from-path src --ro ...

  9. 打开Access时电脑出现蓝屏,错误编号0x00000116的问题解决

    Windows7 64位旗舰版,在打开Access 2013,Onenote 2013时均会出现蓝屏,现就出现蓝屏问题解决方法给大家做一个分享. 步骤: 1.右击我的电脑,打开设备管理器 2.按顺序1 ...

  10. 为 windows 10 右键菜单加打开DOS窗口

    创建一个批处理文件,输入以下行,保存执行即可. echo off reg add "HKCR\*\shell\ms-dos" /ve /d 打开DOS命令 /f reg add & ...