聚合类型与POD类型
Lippman在《深度探索C++对象模型》的前言中写道:
I have heard a number of people over the years voice opinions similar to those of your colleagues. In every case, those opinions could be attributed to a lack of factual knowledge about the C++ language. Just last week I was chatting with an acquaintance who happens to work for an IC testing manufacturer, and he said they don't use C++ because "it does things behind your back." When I pressed him, he said that he understood that C++ calls malloc() and free() without the programmer knowing it. This is of course not true. It is this sort of "myth and legend" that leads to opinions such as those held by your colleagues….
Finding the right balance [between abstraction and pragmatism] requires knowledge, experience, and above all, thought. Using C++ well requires effort, but in my experience the returns on the invested effort can be quite high.
要想C++学得好、用得好,了解编译器在你背后做的事情是很有必要的。
在C++中有一类特殊的类型,聚合类型,可以被看作是纯正的数据类型;聚合类型有一个子集,POD类型,C++可以通过POD类型与其他语言交互。C++11定义了标准的内存模型,POD的概念被淡化,取而代之的是平凡性与标准布局。
在介绍这些类型分类之前,我们先来了解几种初始化。
零初始化
零初始化,顾名思义,就是初始化成零。零是指该类型的零,可能不是每一位都是0的表示。
在以下情形中,零初始化被执行:
static T object;T();T t = {};T{};CharT array[n] = "";
最后一种指的是字符串字面量长度不足数组长度,剩余的部分被零初始化。
零初始化和常量初始化与下面要讲的默认初始化和值初始化不在一个层次上,前者是静态存储期限对象的行为,后者是初始化器的行为。
默认初始化
当一个对象没有初始化器时,它被默认初始化,包括以下情形:
T object;;new T;没有包含在构造函数初始化列表的成员对象。
默认初始化的效果是递归的:
对于类类型,默认构造函数被调用;
对于数组类型,每个元素被默认初始化;
其他情况,什么都不做,对象被初始化为非确定值。
依据上面的规则,全局作用域下的int i;会被默认初始化,i会拥有非确定值,然而我们又知道,作为一个静态存储期限的对象,i的初始值是0。事实上,i先被零初始化,再被默认初始化,第二步中它的0值保持不变。
合成的默认构造函数对每个对象执行默认初始化,即使是显式= default也一样,类中的非类类型对象的初始值是非确定的,参见C++类成员默认初始值。
访问一个非确定值的行为是未定义的,尽管那个对象可能只是一个普通整数,但如果较起真来,程序直接崩溃也是符合预期的。没有什么理由需要使用一个非确定值,如果需要随机数可以用专门的随机数设施。
值初始化
当一个对象的初始化器为空时,它被值初始化,包括以下情形:
T();new T();Class::Class(...) : member() { ... };空的列表初始化。
列表初始化包括前三种把()换成{},以及T object{};,但后者没有对应的圆括号版本,因为T object();是在声明一个函数。
值初始化的行为是:
对于有用户提供的默认构造函数的类型,执行默认初始化;
对于隐式声明或显式
= default默认构造函数的类型,先执行零初始化,再执行默认初始化;如果是数组类型,对每个元素值初始化;
否则,零初始化。
所谓用户提供的构造函数,是指有{...}定义的,= default和= delete不在此列。
值初始化的意义在于它可以提供确定的对象。标准库容器在以容器大小为参数的构造函数中,执行的初始化就是元素类型的值初始化。
聚合类型
聚合类型的概念在C++的发展中有过许多细节调整,这里先根据C++11标准讲解。
聚合类型是数组类型,或满足以下条件的类类型:
没有
private或protected非静态数据成员;没有用户提供的构造函数;
没有基类;
没有虚函数;
没有类内初始化。
注意,这个定义不是递归的——聚合类型的成员完全可以是非聚合类型。
初始化一个聚合类型的方式有:
T object = {arg1, arg2, ...};;T object{arg1, arg2, ...};。
C++14解禁了类内初始化;C++17允许基类,但不能是private、protected或virtual,相应地构造函数不能有继承的,还加了一条不能有explicit的;C++20又把构造函数的要求改回了没有用户声明的构造函数(= default也不行了)。虽然每次修改都有道理,但是频繁修改语言核心真心头疼。至于C99中的指定元素名称的初始化,也在C++20中才进入标准。
聚合初始化的行为是:
所有基类、数组、非静态数据成员,按照在类中的出现顺序与数组下标的顺序,从初始化列表中的对应项拷贝初始化;
除了列表初始化以外,隐式转换都是允许的;
聚合初始化是递归的——如果初始化列表中有嵌套列表,对应项会被列表初始化;
不指定长度的数组可以从初始化列表推导长度,非静态数据成员除外;
静态数据成员与未命名的位域跳过;
初始化列表的长度不能超过需要初始化的基类与成员的数量;
如果长度不够,包括初始化列表为空:
如果有类内初始化,用它;
否则按照列表初始化的规则,对于非类类型和非聚合类类型,值初始化;
对于聚合类型,递归使用该规则;
没有对应初始化项的成员不能有引用类型的,否则报错;
对于联合体,只有第一个成员被初始化。
学习聚合类型的规则,重在理解聚合初始化的行为——初始化要做的就是、只是拷贝构造每一个成员。这样就不难解释一些行为,比如,由于静态数据成员不是对象的一部分,因此在初始化时被跳过;虚函数和虚基类会引入vptr之类的东西,在初始化列表中没有体现,因而不被允许。这种思考方式在C++中是很实用的。
POD类型
在C++11以前,POD(Plain Old Data)类型定义为下列类型之一:
标量类型,包括算术类型(整数与浮点数)、指针、成员指针、枚举类型、
std::nullptr_t(C++11特性,只是为了给标量类型一个完整的定义);POD类型的数组类型;
类类型,满足:
是聚合类型;
没有非POD的非静态数据成员;
没有引用类型的成员;
没有用户提供的拷贝构造函数;
没有用户提供的析构函数。
从C++11起,上述最后一大类修改为:
是平凡类型(见下);
是标准布局类型(见下);
没有非POD的非静态数据成员、
那我们先来看这两个定义。
平凡
一个平凡(trivial)的类型是这样的类型:
符合TriviallyCopyable要求;
有一个或多个默认构造函数,每个都是平凡的(稍后解释)或删除的,至少有一个不是删除的。
对应类型特征(type trait)is_trivial。
TriviallyCopyable的要求是指:
每个拷贝构造函数都是平凡的或删除的;
每个移动构造函数都是平凡的或删除的;
每个拷贝赋值运算符都是平凡的或删除的;
每个移动赋值运算符都是平凡的或删除的;
析构函数是平凡的、非删除的;
TriviallyCopyable类型的数组仍然是TriviallyCopyable的。
这里有很多平凡,我不打算一一列出其要求,它们大致上讲了同一件事:
不是用户提供的;
所在类没有虚拟,包括虚基类和虚函数;
对类型中的每个非静态成员,递归该要求。
平凡的构造函数还有一条:没有类内初始化。
标准布局
平凡规定了对象控制行为,标准布局(StandardLayoutType,is_standard_layout)则规定了对象模型:
所有非静态数据成员都有相同的访问控制等级,即同为
public、同为protected或同为private(这是因为,编译器有权把相同访问等级的成员安排在一起,那样会破坏布局);没有虚拟;
没有非静态数据成员是引用类型;
对基类和非静态数据成员类型递归要求StandardLayoutType;
不能有同一个基类被继承两次,即所谓菱形继承(
virtual早就已经否决了);继承链中只有一个类型有非静态数据成员;
为了不与空基类优化冲突,基类不能有以下类型:
对于非联合类型,第一个非静态数据成员的类型;
对于联合类型,所有非静态成员类型;
对于数组类型,其元素类型;
以及这些类型递归调用这条规则产生的类型,有点计算LL(1)分析算法中FIRST集的味道。
现在可以回到POD类型了。POD是特殊的类型,它有许多非POD类型不具有的性质:
完全与C兼容,但是仍然可以有成员函数;POD类型标准到甚至可以与其他语言兼容;
可以用
std::memcpy拷贝(对于非POD类型,即使满足TriviallyCopyable,用std::memcpy拷贝的行为也是未定义的);有更长的生命周期,从资源获取到资源释放,而非POD类型的是从构造函数结束到析构函数结束;
goto语句不能跳过变量的定义,但POD类型的是允许的;POD类型对象的前部没有填充字节,即对象指针与第一个成员的指针是相等的。
聚合类型与POD类型的更多相关文章
- 关于POD和非POD类型中,list initialization和constructor initialization(未解决)
如果你的成员是POD类型的,那么list initialization和constructor initialization没有任何区别 #include<iostream> using ...
- c++11 pod类型(了解)
啥是POD类型? POD全称Plain Old Data.通俗的讲,一个类或结构体通过二进制拷贝后还能保持其数据不变,那么它就是一个POD类型. 平凡的定义 .有平凡的构造函数 .有平凡的拷贝构造函数 ...
- C++ trivial和non-trivial构造函数及POD类型(转)
原博客地址http://blog.csdn.net/a627088424/article/details/48595525 最近正纠结这个问题就转过来了,做了点补充(参考<深度探索C++对象模型 ...
- C++11 POD类型
POD,全称plain old data,plain代表它是一个普通类型,old代表它可以与c兼容,可以使用比如memcpy()这类c中最原始函数进行操作.C++11中把POD分为了两个基本概念的集合 ...
- POD类型
POD类型 POD全称Plain Old Data.通俗的讲,一个类或结构体通过二进制拷贝后还能保持其数据不变,那么它就是一个POD类型. C++11将POD划分为两个基本概念的合集,即:平凡的和标准 ...
- 3. C++ POD类型
POD全称Plain Old Data,通常用于说明1个类型的属性.通俗的讲,一个类或结构体通过二进制拷贝后还能保持其数据不变,那么它就是一个POD类型. C++11将POD划分为2个基本概念的合集, ...
- 关于C++ 中POD类型的解析
转自: http://liuqifly.spaces.live.com/blog/cns!216ae3a149106df9!221.entry (C++-98:1.8;5)给出的定义:将对象的各字节拷 ...
- C++ POD类型
POD( Plain Old Data)概念: Arithmetic types (3.9.1), enumeration types, pointer types, and pointer to m ...
- Kubernetes的pod控制器及ReplicaSet控制器类型的pod的定义
为什么需要Pod Kubernetes项目之所以这么做的原因: 因为Kubernetes是谷歌公司基于Borg项目做出来的,谷歌工程师发现,他们部署的应用往往存在这进程与进程组的关系.具体说呢,就是这 ...
随机推荐
- 使用Jmeter测试java请求
1.性能测试过程中,有时候开发想对JAVA代码进行性能测试,Jmeter是支持对Java请求进行性能测试,但是需要自己开发.打包好要测试的代码,就能在Java请求中对该java方法进行性能测试2.本文 ...
- stand up meeting 1/12/2016
part 组员 工作 工作耗时/h 明日计划 工作耗时/h UI 冯晓云 UI测试和调整:页面跳转调整 3 查漏补缺,扫除UI b ...
- stand up meeting 1/11/2016
part 组员 工作 工作耗时/h 明日计划 工作耗时/h UI 冯晓云 跑通打印机功能,尝试与pdf读取部分结合;生词本卡片选择简略释义 ...
- 数据结构之循环队列Demo
循环队列 比较简单,循环队列主要是判断队满.队空.有效元素个数 画图说明: 假设:队的长度为5(0-4) 但是实际maxsize为6,需要一个预留空间(不存储元素)做计算 继续添加3个元素后: 出队一 ...
- 详解 ServerSocket与Socket类
(请观看本人博文 -- <详解 网络编程>) 目录 ServerSocket与Socket ServerSocket 类: Socket类: ServerSocket与Socket 首先, ...
- linux之cat 操作
1.查看或创建 cat 1.txt #如果目录有这个文件则会打开查看,没有则会创建 2.压缩空白 cat 1.txt 我是第一行 我是第二 行 cat -bs 1.txt # 变成 cat 1.txt ...
- redis: 主从复制和哨兵模式(十三)
redis 主从复制 最低要求是一主二从(一个主机和两个从机) 主机才能写 从机只能读 只要从机连接到主机 数据就会全量复制到从机 环境配置(同一台机器) 1:配置文件 redis.conf配置如下: ...
- JDBC教程——检视阅读
JDBC教程--检视阅读 参考 JDBC教程--W3Cschool JDBC教程--一点教程,有高级部分 JDBC教程--易百 JDBC入门教程 – 终极指南 略读 三层架构详解,JDBC在数据访问层 ...
- 解决centos ping不通外网
先确认三件事: 一.ip 二.网关 三.dns 一就不说了,设置好本地ip和掩码就行了,二网关 添加默认网关,命令:route add defaule gw 192.168.1.1 这是 你用ro ...
- 【轮询】【ajax】【js】【spring boot】ajax超时请求:前端轮询处理超时请求解决方案 + spring boot服务设置接口超时时间的设置
场景描述: ajax设置timeout在本机测试有效,但是在生产环境等外网环境无效的问题 1.ajax的timeout属性设置 前端请求超时事件[网络连接不稳定时候,就无效了] var data = ...