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标准讲解。

聚合类型是数组类型,或满足以下条件的类类型:

  • 没有privateprotected非静态数据成员;

  • 没有用户提供的构造函数;

  • 没有基类;

  • 没有虚函数;

  • 没有类内初始化。

注意,这个定义不是递归的——聚合类型的成员完全可以是非聚合类型。

初始化一个聚合类型的方式有:

  • T object = {arg1, arg2, ...};

  • T object{arg1, arg2, ...};

C++14解禁了类内初始化;C++17允许基类,但不能是privateprotectedvirtual,相应地构造函数不能有继承的,还加了一条不能有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类型的更多相关文章

  1. 关于POD和非POD类型中,list initialization和constructor initialization(未解决)

    如果你的成员是POD类型的,那么list initialization和constructor initialization没有任何区别 #include<iostream> using ...

  2. c++11 pod类型(了解)

    啥是POD类型? POD全称Plain Old Data.通俗的讲,一个类或结构体通过二进制拷贝后还能保持其数据不变,那么它就是一个POD类型. 平凡的定义 .有平凡的构造函数 .有平凡的拷贝构造函数 ...

  3. C++ trivial和non-trivial构造函数及POD类型(转)

    原博客地址http://blog.csdn.net/a627088424/article/details/48595525 最近正纠结这个问题就转过来了,做了点补充(参考<深度探索C++对象模型 ...

  4. C++11 POD类型

    POD,全称plain old data,plain代表它是一个普通类型,old代表它可以与c兼容,可以使用比如memcpy()这类c中最原始函数进行操作.C++11中把POD分为了两个基本概念的集合 ...

  5. POD类型

    POD类型 POD全称Plain Old Data.通俗的讲,一个类或结构体通过二进制拷贝后还能保持其数据不变,那么它就是一个POD类型. C++11将POD划分为两个基本概念的合集,即:平凡的和标准 ...

  6. 3. C++ POD类型

    POD全称Plain Old Data,通常用于说明1个类型的属性.通俗的讲,一个类或结构体通过二进制拷贝后还能保持其数据不变,那么它就是一个POD类型. C++11将POD划分为2个基本概念的合集, ...

  7. 关于C++ 中POD类型的解析

    转自: http://liuqifly.spaces.live.com/blog/cns!216ae3a149106df9!221.entry (C++-98:1.8;5)给出的定义:将对象的各字节拷 ...

  8. C++ POD类型

    POD( Plain Old Data)概念: Arithmetic types (3.9.1), enumeration types, pointer types, and pointer to m ...

  9. Kubernetes的pod控制器及ReplicaSet控制器类型的pod的定义

    为什么需要Pod Kubernetes项目之所以这么做的原因: 因为Kubernetes是谷歌公司基于Borg项目做出来的,谷歌工程师发现,他们部署的应用往往存在这进程与进程组的关系.具体说呢,就是这 ...

随机推荐

  1. 如何利用 githob 上传自己的网站

    如何搭建自己的网页是每个学前端伙伴不可缺少的一个过程,特意去看过很多如何搭建的教程,但都看不懂觉得很麻烦, 在慢慢的学习中接触到githob,发现了一个大宝藏(如果一个code都不认识githob 那 ...

  2. 数据表记录包含表索引和数值(int范围的整数),请对表索引相同的记录进行合并,即将相同索引的数值进行求和运算,输出按照key值升序进行输出

    此题如果直接使用有序的TreeMap就不需要这样折腾: 1.map的key值唯一性,故就不在需要set集合来去重 2.使用map后利用key的唯一性,把序列号相同的数据直接加在一起,代码会很简洁 pa ...

  3. 动态网页D-html

    BOM(Browser Object Model)浏览器对象模型 window对象(window – 代表浏览器中打开的一个窗口) 1.alert()方法 – 定义一个消息对话框 window.ale ...

  4. CopyOnWriteArrayList线程安全的集合

    CopyOnWriteArrayList是线程安全的集合.本身就是安全的,同时只能被一个进程所访问. 属于JUC并发编程里面的内容. public static void main(String[] ...

  5. AJ学IOS(54)多线程网络之NSOperation重要知识

    AJ分享,必须精品 一:队列的类型与队列添加任务 1: 主队列 [NSOperationQueue mainQueue] 添加到”主队列”中的操作,都会放到主线程中执行. 2:非主队列 [[NSOpe ...

  6. GeoGebra重复手段实现

    1.自定义工具部分可以在网上搜一些别人做的工具,主要是把自己经常做的一些任务做成工具,减少重复过程 2.列表部分的简单操作如图所示,实现对三个点的多项式拟合 3.通过序列指令格式可以做一个好玩的效果, ...

  7. 用Python爬取大众点评数据,推荐火锅店里最受欢迎的食品

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:有趣的Python PS:如有需要Python学习资料的小伙伴可以加点 ...

  8. The equation SGU - 106

    题目链接:https://codeforces.com/problemsets/acmsguru/problem/99999/106 这个题是关于EXGCD特别好的一个题目.题目大意:有一个等式ax+ ...

  9. springboot前后端分离跨域

    @Configurationpublic class CrossConfig implements WebMvcConfigurer { @Override public void addCorsMa ...

  10. Flutter环境安装,ios真机调试

    MAC: 下载Flutter,官网的可能很慢.可以去我的网盘下载, 提取码: 3t6y. 下载完的包会在~/Downloads目录下,我们移到~/opt/flutter目录下. mkdir ./opt ...