深度探索cpp对象模型(1)(2)

 

第一章 关于对象

指针其实存的就是一个地址,不管啥类型的指针大小都是一样的,对不同类型的指针,怎么知道要读多少字节,这是编译器干的事情,指针类型告诉编译器,我到底要读多少字节。

32位,指针大小4个字节,64位8个字节
由此引申开来,不能用void * 操作对象,针对void *指针的static_cast,其实是一种编译器指令,并没有改变一个指针所含的地址 

第二章 构造函数语意学

1)默认构造函数

主要还是参考 prime

当你没有默认构造函数的时候,且你没有定义其他构造函数,啥时候编译器不会为你合成一个呢,也就是定义为删除?

1)  类的某个数据类成员,它的析构函数是删除或者无法访问的(access level)

2) 类的某个数据类成员无法默认构造的时候

         a)它就是无法默认构造,比如有个其他的构造函数了,却没定义默认构造函数

         b)它是个引用,那么它必须得显式初始化,然后又没给它类内初始值

         c)   它是个const,但是没有显式的默认构造函数,(const 对象必须要初始化,但是一个有显式的默认构造函数的const 也是ok的, 比如 const string s1; 不会报错,它可能会有一个编译器合成的默认构造函数,但是对于const 对象来说,不顶事),且没有类内初始值 

以上这些数据类成员包括基类    (prime p451)

编译器合成版本的默认构造函数会为你干这3样事:

1. 给你的数据类成员 (包括基类) 默认初始化

2. 然后还会给你一个vptr帮你初始化好,指向类的虚函数表 (共享一个)

3 . 还有一个虚基类指针(以后补充) 只有这两个指针编译器会帮你初始化好

假如你自己定义的默认构造函数也好,别的构造函数也好,应该要有这些3个内容,却缺了点啥,编译器都会帮你补上,顺序按声明顺序(先基类,再自己的数据成员,两个指针不知道在哪一块 最开始or最下面 以后补充),那假如你没有默认构造函数,编译器也不给你合(也就是被定义为删除的)你又默认初始化了一个东东 那就gg,编译器报错。

并且下面这段代码也会报错:

class test{
public:
    test(int _a):a(_a){}

private:
    int a;
};

class test1{
public:
    test a;
    int b;
    test1(int i):b(i){}
};

int main()
{
    test1 ab(1);


}

因为test1的构造函数test1(int i)体内实际会调用test的默认构造函数(编译器帮你干的),但是test又没有默认构造函数,所以gg!

然后有个点,内置类型的,比如整型 浮点型 字符型 指针 内置类型数组 都不会被编译器初始化,这些是你要干的事情,然后还有个tips,global区一开始会被清0,local和heap不会清0,所以是上一次的遗留物

2)拷贝构造函数

什么情况会调用copy构造函数:  

1.用一个对象作为参数初始化另一个对象时.

2. 对象作为函数参数时, 会用参数对象在函数作用域构造一个新的对象.

3. 对象作为返回值时, 会用函数内部的对象在返回值所在作用域构造一个新的对象.

注意, 2, 3不一定会发生, 因为可能会存在右值参数, 返回值优化等, 具体情况不做详述.

 如果不显式定义复制构造函数, 编译器有两种复制对象的方法: bitwise copy和default memberwise copy, 区别如下:

类的copy整体思路是用 default memberwise initialization 实现的,具体点就是以一个个member的搞,对于内置类型的,int,指针,数组,是以bitwise copy实现的,而对于类类型,则是递归的调用的memberwise initialization实现的

bitwise copy(位逐次拷贝),就是复制一个个位,并不调用copy构造函数, 可能的实现方式如利用memcpy等, 因此效率高, 复制出的对象和原对象完全相同.

假如类靠bitwise copy就ok的话,就万事大吉了,但是有些情况,bitwise copy会出错,这时候,编译器会帮你隐式生成一个“有用的”copy构造函数。

插一句,bitwise copy也好,“有用的”copy构造函数也好,其实都可以理解编译器帮我们定义的一个copy构造函数,只是前者是“没用的”,所以编译器不会帮我们合成于程序中,我猜可能bitwise copy 可能是一个默认的机制?但是对于一个string类,它的data是int 和char * ,假如我们没用定义copy构造函数,显然它的实现会是bitwise copy,这时候我们对一个对象用拷贝来初始化,程序是不会报错的

那啥时候程序会gg呢?当 copy构造函数是delete的时候,

根据prime p450

当类的数据类成员析构函数(拷贝构造函数)是删除的或不可访问的,这个类的copy构造函数是delete的。

ok,回来,下一个问题,啥时候编译器会搞个有用的copy构造函数呢?

  1. 当类含有类对象成员(这里有一个,不用要求全部), 且这个成员含有copy构造函数时(不论是编译器合成的(合成的要是有用的!)还是显式定义的).
  2. 当类继承自一个基类, 并且基类含有copy构造函数时(不论是编译器合成的还是显式定义的).
  3. 当类含有虚函数时.
  4. 当类有虚基类时.

上面的情况很容易理解. 对于1和2, 由于copy对象时, 要copy数据成员和基类, 既然它们提供了copy构造函数, 就可以认为需要在它们的copy构造函数中进行某些bitwise copy无法实现的操作, 因此不能采用bitwise copy.

对于3, 由于含有虚函数, 所以需要初始化对象的vtpr, 而vptr的值显然不一定等于参数对象的值, 例如用子类对象初始化父类对象时. 所以bitwise不能满足需求.

对于4, 由于含有虚基类, 父子基类的内存布局可能存在区别, 更不能采用bitwise copy.

ok 总结一哈,一个编译器合成的copy构造函数,大部分情况下是bitwise copy,但是当我们有个类对象成员,它的copy构造函数不是bitwise copy的时候,(类对象成员包括基类),或者当类有虚函数,虚基类的时候,不能对那两指针简单粗暴的bitwise copy,这时候会对那些刺头进行 不是bitwise copy, 具体做法是调用  类对象成员的 “有用” copy构造函数 对那两指针进行显式设定

 

3)程序转化语意学

尽管在程序中可以使用不同的形式来初始化一个类对象, 但在编译阶段都会被转化成相同的形式. 例如:

class X;
X x0(paras);
X x1 = X(paras);
X x2(x0);
X x3 = x0;
X x4 = X(x0);

会被转化为:

X x0; // 声明但不初始化
X x1; // 声明但不初始化
X x2; // 声明但不初始化
X x3; // 声明但不初始化
X x4; // 声明但不初始化

// 调用构造函数初始化对象
x0.X::X(paras)
x1.X::X(paras)

// 调用复制构造函数初始化对象
x2.X::X(x0)
x3.X::X(x0)
x4.X::X(x0)

参数初始化:

void foo(X x0)

调用这个函数 我们在程序里写的是  X xx;foo(xx); 但实际编译器的实现 两种方法是: 看书  p62  

  返回值的初始化:
看书  主要思路就是一个返回值的函数   典型的编译器实现方法是在函数参数里面加一个引用的参数,这个参数用来放return 的值,靠拷贝构造函数来实现,然后函数返回void  

然后有两种优化方法: 1)码农的脑洞版:定义一个计算用的构造函数,参数就是原函数的参数,然后函数体就是一个return 这个构造函数的返回值, 然后编译器帮你实现的就是(见上) 一个引用的参数,直接计算,这样就省了一个拷贝构造

2)编译器层面的优化:就是NRV  (named return value 具名返回值?maybe) 就是把我原来不是要加一个引用的参数,然后把我要返回的值,拷贝到这个参数里面来嘛,现在我就把函数体里面这个返回值的对象,同时变成我引用参数的对象,这样就省了一个拷贝构造 (有个tip,要想编译器帮你NRV优化,必须要显示定义拷贝构造函数,我也不懂为啥?)

可能是这个原因

4)成员们的初始化队伍

构造函数的初始化列表,1个是可以提升效率,还有一个是你有时候必须要这么做。

1. 提升效率:

因为编译器会对构造函数进行扩充,先对数据成员进行初始化(默认构造函数),再执行构造函数体,那么就会先进行初始化,然后进行赋值操作(可能还会有先构造一个临时对象,然后进行赋值操作,再析构这样的操作),如果用初始化列表,就能初始化一步到位(实际编译器转换后的代码是先执行初始化列表(对应的构造函数),再执行函数体的代码),初始化顺序是类定义时候数据成员的声明顺序决定,不是由列表排列顺序决定。(不过,数据成员是内置类型的话无所谓,你放在初始化列表里面,还是放在函数体里面,都ok,因为编译器不会对一个内置类型初始化作什么动作)。

2. 必须这么做

比如有引用或者const对象,这种必须要初始化的成员的时候,还有就是你要调用某个成员的非默认构造函数,或者这个成员它没有默认构造函数的时候(如果这样,会报错)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值