第一章.关于对象
前言
c语言中数据和处理是分开声明的,也就是说语言本身没有支持数据和处理的关联性,我们把这种程序方法称为程序性。
而有一组以功能为向导的函数所驱动,共同处理共同的外部数据,这种称为对象性。
对象与成本
关于c++的对象,有人说由程序性到对象会增加成本,我们看看是否真的是这样的:
先考虑对于一个拥有成员变量的类,这个并没有增加成本。然后当类中增加了类成员函数,对于类成员函数,类成员函数虽然包含在类声明中,但是不属于对象。关乎类成员函数中有一个inline,当类成员函数被标记为内联,在每一个对象调用内联函数都会产生一个函数实例,这么做提高了效率,这个并不是成本的增加。
真正影响成本的是由virtual引起的:
Virtual function机制,用以支持一个有效率的“执行期绑定”。
Virtual base class用以实现“用以实现多次出现在继承体系的base class,有一个单一而共享的实例”。
c++的对象模式
简单对象模型:
目的:尽量减轻c++编译器设计的复杂度而开发出来的,赔上的是空间和执行期的效率。
实现原理:由一张指针表组成,所有指针都指向对象成员。
应用:c++指向成员的指针。
表驱动对象模型:
目的:为了使class所以的对象具有一致性。
实现原理:产生两个指针,一个指向data number table和一个 number function table。
应用:虚表。
c++对象模型:
每一个class产生出一堆指向virtual function的指针,放在表格里,称为虚表。
每一个class object被安插一个指针,指向相关的虚表。虚表的设定交由构造函数,每个类关联的 type_info object也存放在虚表之中,通常放在表格的第一个slot中
//其实关于多继承还是蛮复杂的
当多继承出现时,布局可能是这样的:
每一个derived class object内的一个slot指出base class的地址。
优点是对象不会因为base class的改变而影响。
缺点是由于间接性而导致的空间和存储时间上的额外负担。
构造函数的语义学
defaultconstructor的构造操作
c++标准说:当用户未曾创建构造函数时,编译器会提供给用户一个trivialconstructor。
那么什么时候会提供呢?
当编译器需要一个constructor的时候。
一共有四种情况:
- 当一个类中包含class object number,并且这个class object number含有一个 default constructor。
- 当一个类含有base class的时候,并且这个 base class 具有default costructor的时候。
- 当一个类具有virtual function的时候。
- 当一个类是以virtual继承的方式继承的时候。
Copy constructor的构造操作
其实关于default constructor我都知道做了些什么,关于这个copy,我们会在哪里用到它呢?
- 拷贝构造
- 函数的值传递和函数的返回值构造。
Copyconstructor的内部构造方式:
把每一个内建的data member的值拷贝一份给另一个object,如果data member也是一个类,则递归,这个叫memberwiseinitialzation。(成员逐次初始化?)
而实际上Arm告诉我们说,概念上这,对于一个classX这个操作是由copy constructor实现的。而实际上一个良好的编译器可以为大部分class object产生bitwrist copy ,因为他们是具有bitwrist copy语义的。
那么什么叫bitwrist copy呢?位逐次拷贝。
我发现位逐次拷贝和我们所说的浅拷贝差不多,但是真的差不多么?我们一会再看
我们先说一下什么时候不会展现bitwristcopy,而由编译器为你创建一个copy construct?
1.当一个类中含有类对象,而这个类对象中含有copy construct的时候。
2.当base class中含有一个copyconstruct中的时候。
3.当class中含有virtualfunction的时候。(因为虚表的创建是由三大构造函数指明的。这里没算上移动构造函数)
4.当class派生自一个继承串,而其中有一个或多个virtual base class 的时候。(这种情况和其他三种不太一样,因为其他都发生平行赋值就会引发copy construct(也就是说类对象与类对象之间的),而第四种是发生在派生类与父类之间的赋值才会体现,因为virtual base class 是动态的,由于各个编译器实现不同我们可能会改变offset或者其他如virtualbase class 的位置啊,什么的?这完全取决于编译器实现,但是,一定会引发copy construct的产生是毋庸置疑的)
浅拷贝与bitwrist copy:
浅拷贝与bitwrist copy并不是一个东西,浅拷贝主要是用来说明在类成员中存在指针的情况下,浅拷贝会造成的种种原因的一个表象说法,而关于bitwrist copy用来针对与copyconstructor的一个大范围的一种copymeathod。
关于bitwrist copy与memberwristcopy的关系,我还有一点自己的理解就是memberwristcopy是依赖bitwrist copy的,对于基本类型我们只能通过bitwrist copy,因为基本类型没有copy constructor。
对于memberwrist copy,bitwristcopy,我看到的第一个想法就是关于内存泄漏方面,事实上来说,有点关系,只不过这个是专门用来描述对象模型的,对于内存泄漏会有一定帮助但是帮助不大。
程序转化语义学
这一章讲了,编译器为我们做了什么,使得我们在编写程序的时候“得心应手”,与编译器的优化。
显示初始化操作:
我们在显示初始初始化的时候是如何书写的,如下:
已知:
X x0;
则
X x1(x0);
这段代码被程序转化成什么样呢?
1.x1被重新定义,剥夺其初始化操作:
X x1;
2.调用X的copy construct
x1.X::X(x0);
参数的初始化:
当我们在传参的时候直接通过在参数内构造临时对象时,也会分为两个步骤:
1.将构造方法与函数调用剥离,构造一个临时对象。
2.将临时对象传入函数。
也就是说我们会依次调用两个构造函数,所以会造成什么后果你懂的。
但是有的编译器会把实际参数直接构建在应该的位置上。也就说我们少构建一次。
返回值的初始化:
对于一个X bar();
优化方式如下:增加一个额外参数,类型为class object的一个引用(X&),在返回指令前安插一个copy construct从而获得返回值。
如下:
X bar();
X vczh =bar();
伪代码
Voidbar(X& _result){
X x;
_result.X::X(x);
Return;
}
//剥夺初始化的权利
Xvczh;
bar(vczh);
在使用者层面做优化:
这个没什么说的了,所谓使用者优化,就是垃圾程序员写的垃圾代码最后由编译器收拾烂摊子,收拾的好坏,能不能收拾,这个呵呵。
编译器层面做优化:
这里介绍了Name Return Value (NRV)优化,我所理解的NRV优化就是省去代码中最后作为返回结果的临时对象,而通过返回值的初始化,直接对返回值进行修改。从而减少一次对象构造的机会。
我在想为什么NRV优化对于编译器这么重要,我们也可以写成像NRV优化出来一样的代码啊,我们也可以剥夺初始化的权利,引用调用,我觉得实际上,这个代码对于审阅的人来说太复杂了不是吗?他需要顺着你的思维去了解代码,要是猪队友怎么办?他不懂,那么糟糕了。我们在写代码的时候不要过分追求这些,有些东西底层帮你做了,那么你就负责写出能被编译器尽量优化的和能被队友审阅的代码,那么就ok了。
成员初始化列表
1.当初始化一个reference member时。
2.当初始化一个const member是
3.当调用一个基类的构造函数的时候。
4.当调用一个member class的constructor,而他拥有一组参数的时候。
list中的项目顺序是由class中的member声明顺序决定的,不是由,initializationlist中的排列顺序觉定的。
List是先于函数体内构造的,所以为了防止编译器为我们在构造函数体内添加东西,我们应该在list中构造而不是选择在函数体内构造。
尽量不要在list使用类成员函数对类成员进行初始化,你不知道成员函数对类对象的依赖性有多高。