C++统一初始化语法(列表初始化)
引言
要是世上不曾存在C++14和C++17该有多好!constexpr
是好东西,但是让编译器开发者痛不欲生;新标准库的确好用,但改语法细节未必是明智之举,尤其是3年一次的频繁改动。C++带了太多历史包袱,我们都是为之买账的一员。
我没那么多精力考虑C++14/17的问题,所以本文基于C++11标准。
知其所以然,是学习C++越发复杂的语法的最佳方式。因此,我们从列表初始化的动机讲起。
动机
早在2005年,Bjarne Stroustrup就提出要统一C++中的初始化语法。这是因为在C++11以前,初始化存在一系列问题,包括:
4种初始化方式:
X t1 = v;
、X t2(v);
、X t3 = { v };
、X t4 = X(v);
;聚合(aggregate)初始化;
default
与explicit
;……
虽然每一个都有办法解决,但加在一起将会变得非常复杂,对编译器和开发者都是负担。换句话说,唯一的需求就是一种统一的初始化语法,其适用范围能涵盖先前的各种问题。
于是,列表初始化诞生了。
语法
正因为列表初始化是为解决初始化问题而生,列表初始化的适用范围是任何初始化。你能想到的都写写看,写对就是赚到。
当然,全凭感觉是行不通的,还是得讲点道理。列表初始化分为两类:直接初始化与拷贝初始化。
在直接初始化中,无论构造函数是否explicit
,都有可能被调用:
T object { arg1, arg2, ... };
,用arg1, arg2, ...
构造T
类型的对象object
——参数可以是一个值,也可以是一个初始化列表,下同;Class { T member { arg1, arg2, ... }; };
,构造member
成员对象——花括号的优势在这里体现出来,因为如果是圆括号的话member
会被看作一个函数;T { arg1, arg2, ... }
,构造临时对象;new T { arg1, arg2, ... }
,构造heap上的对象;Class::Class() : member{arg1, arg2, ...} {...
,成员初始化列表——除了2以外,其余都与用()
初始化没有区别。
在拷贝初始化中,无论构造函数是否explicit
都会被考虑,但是如果重载决议为一个explicit
函数,则此调用错误:
T object = {arg1, arg2, ...};
,与直接初始化中的1
类似,除了explicit
以外都相同,operator=
不会被调用;object = { arg1, arg2, ... }
,赋值语句,调用operator=
;Class { T member = { arg1, arg2, ... }; };
,与直接初始化中的2
类似,explicit
同理;function( { arg1, arg2, ... } )
,构造函数参数;return { arg1, arg2, ... } ;
,构造返回值;object[ { arg1, arg2, ... } ]
,构造operator[]
的参数;U( { arg1, arg2, ... } )
,构造U
构造函数的参数。
4~7可以概括为,在该有一个对象的地方,可以用一个列表来构造它。这句话不是很严谨,因为除了operator()
和operator[]
以外,其他运算符的参数都不能用列表初始化。
还有一个要注意的地方,是列表初始化不允许窄化转换(narrowing conversion),即可能丢失信息的转换,如float
转换为int
。
#include <iostream>
#include <utility>
struct Test
{
Test(int, int)
{
std::cout << "Test(int, int)" << std::endl;
}
explicit Test(int, int, int)
{
std::cout << "explicit Test(int, int, int)" << std::endl;
}
void operator[](std::pair<int, int>)
{
std::cout << "void operator[](std::pair<int, int>)" << std::endl;
}
void operator()(std::pair<int, int>)
{
std::cout << "void operator()(std::pair<int, int>)" << std::endl;
}
};
Test test()
{
return { 1, 2 };
}
int main()
{
Test t{ 1, 2 };
Test t1 = { 1, 2 };
Test t2 = { 1, 2, 3 }; // error
t[{ 1, 2 }];
t({ 1, 2 });
}
initializer_list
列表不是表达式,更不属于任何类型,所以decltype({1, 2})
是非法的,这还适用于模板参数推导。但是在以下几种情况中,列表可以转换成std::initializer_list<T>
实例:
直接初始化中,对应构造函数参数类型为
std::initializer_list<T>
;拷贝初始化中,对应参数类型为
std::initializer_list<T>
;绑定到
auto
上(列表元素类型必须严格一致),包括范围for
(range for)循环——当绑定auto&&
时,变量的实际类型为std::initializer_list<T>&&
,这是转发引用的特例。
std::initializer_list
是为列表初始化提供的特殊的工具,是一个轻量级的数组代理(proxy),其元素类型为const T
。虽然你能在<initializer_list>
中看到std::initializer_list
类模板的实现,但它实际上是与编译器内部绑定的,你无法用一个自己写的相似的类替换它(除非改编译器)。
std::initializer_list
有构造函数、size
、begin
和end
函数,用法与其他STL顺序容器类似。迭代器解引用得到const T&
类型,元素是不能修改的。
std::initializer_list
带来的最明显的进步就是STL容器可以用列表来初始化,无需再写那么多push_back
了。
重载决议
struct Test
{
Test(int, int)
{
std::cout << "Test(int, int)" << std::endl;
}
Test(std::initializer_list<int>)
{
std::cout << "Test(std::initializer_list<int>)" << std::endl;
}
};
如果我写Test{1, 2}
,哪个构造函数会被调用呢?回答这个问题,需要对与列表相关的重载决议有所了解。
对于涉及到构造函数的列表初始化(不涉及到的包括聚合初始化等),各构造函数分两个阶段考虑:
如果有构造函数第一个参数为
std::initializer_list
,没有其他参数或其他参数都有默认值,则匹配该构造函数(这里似乎允许窄化转换,我测试起来也是如此)——std::initializer_list
优先级高;否则,所有构造函数参与重载决议,除了窄化转换不允许,以及拷贝初始化与
explicit
的冲突依然有效。
所以上面那段程序中Test{1, 2}
会匹配第二个构造函数。
如果有多个std::initializer_list
重载呢?众所周知,重载决议中参数转换有完美、提升、转换三个等级,std::initializer_list
参数的转换等级定义为所有元素中最差的(不允许窄化转换),然后找出等级最高的调用,如果有多个则为二义调用。
如果没有std::initializer_list
重载呢?由于从列表到参数本身就是转换,属于最差的等级,如果有多个函数可以通过参数转换后匹配,则该调用就是二义调用;只有当只有一个函数可行时才合法。
总结
列表初始化是一种万能的初始化语法,适用范围广导致其规则比较复杂,我们应当结合其动机来理解标准规定的行为。
列表初始化包括直接初始化与拷贝初始化,后者涵盖了参数与返回值等情形。当我们不想要隐式拷贝初始化时,要用explicit
关键字来拒绝。
列表不属于任何类型,但一些情况下可以转换成std::initializer_list
。在重载决议中,std::initializer_list
有更高的优先级。
C++统一初始化语法(列表初始化)的更多相关文章
- initializer_list 列表初始化
initializer_list 列表初始化 用花括号初始化器列表初始化一个对象,其中对应构造函数接受一个 std::initializer_list 参数. #include <iostrea ...
- c++11——列表初始化
1. 使用列表初始化 在c++98/03中,对象的初始化方法有很多种,例如 int ar[3] = {1,2,3}; int arr[] = {1,2,3}; //普通数组 struct A{ int ...
- C++新标准:列表初始化
一.列表初始化意义 C++新标准为vector提供了一种新的初始化方式:列表初始化.适用于知道多个成员具体值的情况. 二.列表初始化用法 /*1.空vector<int>*/ vector ...
- 列表初始化(list initialization)
列表初始化啊就是大括号来初始化: 列表初始化的好处:
- 【ZZ】C++11之统一初始化语法 | 桃子的博客志
C++11之统一初始化语法 | 桃子的博客志 https://taozj.net/201710/list-initialize.html 在当前新标准C++11的语法看来,变量合法的初始化器有如下形式 ...
- 大括号之谜:C++的列表初始化语法解析
有朋友在使用std::array时发现一个奇怪的问题:当元素类型是复合类型时,编译通不过. struct S { int x; int y; }; int main() { int a1[3]{1, ...
- 列表初始化 分析initializer_list<T>的实现
列表初始化(1)_统一初始化 1. 统一初始化(Uniform Initialization) (1)在C++11之前,很多程序员特别是初学者对如何初始化一个变量或对象的问题很容易出现困惑.因为可以用 ...
- C++11常用特性介绍——列表初始化
一.列表初始化 1)C++11以前,定义初始化的几种不同形式,如下: int data = 0; //赋值初始化 int data = {0}; //花括号初始化 int data(0); / ...
- C++ Union妙用(将列表初始化用于数组元素)
Union是个不被注意的关键字,意为联合体,这是个诡异的名字.若不是为了继承C语言,它也不会出现在C++中(虽说,union在C++中得到了扩充,完成了接近类的功能).它的作用主要是节省内存空间,在嵌 ...
随机推荐
- 从 Socket 编程谈谈 IO 模型(三)
快过年啦,估计很多朋友已在摸鱼的路上.而我为了兄弟们年后的追逐,却在苦苦寻觅.规划,导致文章更新晚了些,各位猿粉谅解. 上期分享,我们结合新春送祝福的场景,通过一坨坨的代码让 BIO.NIO 编程过程 ...
- C语言中static extern的使用
10:30:22 2019-08-20 基础不牢 瞬间爆炸 参考资料:https://blog.csdn.net/ts_54eagle/article/details/4418627 https:// ...
- Markdown语法详解-cnblog
博客的重要性 博客,英文名为Blog,它的正式名称为网络日记. 为什么要写博客? 需要总结和思考.有时候我们一直在赶路,却忘了放慢脚步 提升文笔组织能力 提升学习总结能力 提升逻辑思维能力 帮助他人, ...
- python3(二十四) subClas
""" 继承的多态 """ __author__ = 'shaozhiqi' # -----------------父类---------- ...
- python3(二十) module
# 在Python中,一个.py文件就称之为一个模块(Module) # 1.最大的好处是大大提高了代码的可维护性. # 2.可以被其他地方引用 # 3.python内置的模块和来自第三方的模块 # ...
- JS数据结构与算法 - 剑指offer二叉树算法题汇总
❗❗ 必看经验 在博主刷题期间,基本上是碰到一道二叉树就不会碰到一道就不会,有时候一个下午都在搞一道题,看别人解题思路就算能看懂,自己写就呵呵了.一气之下不刷了,改而先去把二叉树的基础算法给搞搞懂,然 ...
- 2020年iOS进阶面试题总结(一)
准备找工作的你,可以看看,复习复习!! 1.说一下OC的反射机制 在动态运行下我们可以构建任何一个类,然后我们通过这个类知道这个类的所有的属性和方法,并且如果我们创建一个对象,我们也可以通过对象找到这 ...
- 【Java】 语言基础习题汇总 [1] 基础概念到数组
1 JDK JRE JVM 三种之间的关系,以及JDK JRE 包含的主要结构有哪些? JDK = JRE + 开发工具 javac.exe java.exe javadoc.exe等等 JRE = ...
- 绕过CDN查找真实 IP 姿势总结
返回域名解析对应多个 IP 地址,网站可能部署CDN业务,我们就需要bypass CDN,去查找真正的服务器ip地址 0x01.域名搜集 由于成本问题,可能某些厂商并不会将所有的子域名都部署 CDN, ...
- EFCore.Sharding(EFCore开源分表框架)
EFCore.Sharding(EFCore开源分表框架) 简介 引言 开始 准备 配置 使用 按时间自动分表 性能测试 其它简单操作(非Sharing) 总结 简介 本框架旨在为EF Core提供S ...