c++23中的新功能之九确定性定义顺序

一、基础概念的介绍

学习c++的开发人员,可能一开始就接触过POD这个概念。POD,Plain Old Data,为了和原来的c结构类型兼容,它一般指基本数据类型、指针、 union 、数组、构造函数是 trivial 的 struct 或者 class。说白了就是可以和C编程互换。但是从c++11开始,这个概念就被丢弃了。但其相关的一些萃取函数(std::is_pod)到c++20才正式丢弃。
其实这也是标准演进的结果,一些旧的概念在更新标准前,发现已经被新出现的一些概念或者概念组合替代,那么这些老的概念存在的䪨就不大了。在前面的分析过程中,经常遇到trivial(平凡)类型的概念,而在c++标准中又有“standard layout”标准布局。POD其实就是这两个概念的组合。
Trivial 类型在前面分析过,就是构造函数是编译自己特定合成(就是默认),没有虚基类和虚函数,内部成员也要满足上述要求;而所谓的标准布局,恰恰是不使用C语言不支持的c++的特性。标准布局类型的判断条件为:
1、无虚基类和虚函数
2、无引用成员变量
3、非静态成员保持相同的访问控制权限(公有、保护或者私有)
4、基类和内部非静态成员同样为标准类型布局
5、子类第一个非静态成员变量类型要与基类不同
6、子类中没有非静态成员变量且最多只有一个基类含非静态成员变量 或者 没有定义非静态数据成员的基类。
弄明白了基本概念再却理解丢弃POD就清楚了很多。

二、内存定义顺序

说起定义顺序,其实非常好理解,你就可以理解成排队打饭,排得前面的自然、应该、必须排在前面先打饭。而后来的,不好意思,你只能排在后面。是不是有点数据结构队列的意思。可是,银行不这么搞,他觉得如果有人给他存储一个亿,就可以插队,其实就是VIP。当然还有VIP中P,这个不讨论。那看下面的定义:

struct A
{
    int a;
    int b;
    int c;
};

在老的c++标准中,这三个变量一定是严格按顺序在内存中排列,因为它符合标准布局类型(早期的c++98要求必须是同一访问块,即同为公有或者其它且在一起),注意把这个数据结构改一下:

struct A
{
private:
    int a;
  protected:
    int b;
  public:
    int c;
};

它不是标准布局类型了,为什么,因为他们的访问控制不同了。在标准里,理论上讲,a,b,c三个的顺序可以是随意的。可是,在前面反复说过,编译器厂商没人愿意没事儿找事。本来大家好好排队一齐吃果果,你非要要求我们胡乱横插,这不是吃药的节奏是啥?
所以基本上,编译器厂商在这方面都是忽视是标准的。仍然是严格的按照顺序走。有兴趣可以看看offsetof()这个函数,它的要求必须是严格的标准布局类型。
本质上,标准是为了给编译器厂商一个自由度,意思是我不太逼迫你怎么做。看上去有一种“我都为你好”的味道。可编译器厂商却不这么认为,他认为标准就是在挖坑,不过你挖你的,哥们不上当。因为这种行为天然是违反人类的直觉的。没有人会没事找事增加烦恼。即使有个别编译器厂商对public、private和protected增加了一些排序选项,但基本也没人用。
标准的大佬儿们于是妥协了,对于相同级别的访问变量可以严格按顺序不再按块了。可不同访问级别仍然允许自由度,可终究没编译器厂商理他们,他们只好又撤退了一步,不同的的访问级别也可以了。也就是说,定义顺序和实际的内存顺序从标准到编译厂商进行了统一。
但是,凡事都有但是,标准仍然未对一些嵌套类进行规定,即:

struct In {int i;};
struct Out : In {int o;} out;

这out对象中的o和i两个的顺序就无法与标准对齐。
所以,大佬儿的颜面还是要有的,不能一下子都抛弃了不是。慢慢来,可能还得需要十年。

三、总结

一些旧的事物一旦形成成法,就很难修改了。这个涉及到的问题太多了,方方面面,不一定是技术本身的问题。所以历史上凡是改革都特别的艰难就是这个原因。明明实际情况和标准有着不同,可就是无法完全统一。你干你的,我干我的。反正,编译器厂商在这方面不听你标准的,你也没办法。就是这样,谁干活不夹带点儿私货,想让人多干活还不给好处的事儿,基本不好推下去。

猜你喜欢

转载自blog.csdn.net/fpcc/article/details/131357437