关于对象
概述
- C语言中,数据和处理数据的操作是分开的。
- C++中,可能采用独立的抽象数据类型。
加上封装后的布局成本
virtual function
- 用以支持一个有效率的“运行期绑定“。
virtual base class
- 用以实现”多次出现在继承体系中的基类有一个单一而被共享的实例“。
C++对象模式
简单对象模式
- 一个对象是一系列的slots,每个slots指向一个成员。
- 成员按其声明顺序,各被指定一个slots。
- 每个数据成员或函数成员都有一个slots。
表格驱动对象模式
- 将所有与成员相关的信息抽出来,放在一个数据成员表中和一个成员函数表中。
- 类对象本身则内含指向这两个表格的指针。
C++对象模式
- 非静态数据成员被配置在每个类对象之内,静态数据成员则被放置在个别类对象之外,静态和非静态成员函数也被放置在个别类对象之外。
- 类中有虚函数的话:
- 每个类产生出一堆指向虚函数的指针,放在表格中,这个表格叫
virtual table
(vtbl)。 - 每个类对象被安插一个指针,指向相关的
virtual table
(vptr)。 - vptr的设定和重置都有每个类的构造、析构和拷贝赋值运算符自动完成。
- 每个类所关联的对象type_info也由
virtual table
指出来。
- 每个类产生出一堆指向虚函数的指针,放在表格中,这个表格叫
- 类是虚拟继承的情况下(棱形继承中的基类为虚):
- 基类不管在继承串联中被派生多少次,永远只会存在一个实例。
关键词差异
- C struct在C++中(而不是class关键字)的一个合理用途,是当要传递“一个复杂的类对象的全部或部分”到某个C函数去时,struct声明可以将数据封装起来,并保证拥有与C兼容的空间布局。
- 然而这项保证只在组合的情况下才存在,如果是继承,编译器会决定是否有额外的数据成员被插到基类子对象中。
构造语意学
默认构造函数
- 一般当一个类没有任何用户声明的构造时,编译器将会隐式声明一个默认构造函数出来,而一个被隐式声明出来的默认构造函数,将会是无用的默认构造函数。
- 而当编译器需要的时候,则会合成出来一个默认构造函数。这个合成出来的默认构造函数,只执行编译器所需要的行为。这种合成的构造函数将会是有用的默认构造函数。
- 下述四种有用的默认构造的情况。
一个类有“带有默认构造函数”的类对象成员。
- 即类X没有任何构造函数,但它内含一个对象成员,这个对象成员有默认构造函数。则类X的隐式合成的默认构造函数是一个有用的默认构造函数。
- 不过这个合成操作只有在构造真正需求被调用时才会发生。
- 在C++不同的编译模块中,编译器如何避免合成出多个默认构造函数?
- 解决方法是把合成的默认构造函数、拷贝构造函数、析构函数以及拷贝赋值运算符都以内联的形式完成。
- 因为一个内联函数有静态链接,不会被文件以外者看到。
- 如果函数太复杂,不适合做成内联,就会合成出一个显示的非内联静态实例。
“带有默认构造”的基类
- 如果一个没有任何构造的类派生自一个“带有默认构造”的基类,那么这个派生类的默认构造函数会被视为有用的,并因此需要被合成出来。
- 这个合成的默认构造函数将会调用基类的默认构造。
- 如果这个类有多个构造函数,但其中没有提供默认构造函数,编译器会扩张现有的每一个构造函数,将会“用以调用所有必要的默认构造函数”的程序代码加进去。因而它也就不会合成一个新的默认构造了。
“带有一个虚函数”的类
- 带有一个虚函数的类需要合成出默认构造。
- 类声明(或继承)一个虚函数。
- 类派生自一个继承串链,其中有一个或更多的虚基类。
- 上面3种情况,由于没有用户声明的构造函数,所以编译器会合成出一个默认构造函数。
- 对一个有虚函数的类来说,会在编译期间发生一些扩张行为:
- 一个
virtual table
会被编译器产生出来,内放类的虚函数地址。 - 在每一个类对象中,一个额外的成员指针(即vptr)会被编译器合成出来,内含相关类的虚函数表的地址。
- 而为了让这个虚函数机制生效, 编译器必须为每一个类的vptr设定初值,放置适当的虚函数表的地址。对于类定义的所有构造函数来讲,编译器会安插一些代码来做这些事情。对于那些未声明任何构造函数的类,编译器会为它们合成一个默认构造函数,以正确地初始化每一个类对象的vptr。
- 一个
“带有一个虚基类”的类
- 对于虚基类的实现,不同的编译器之间有很大的差异。然而,每一种是实现法的共同点在于必须使虚基类在其每一个派生类对象的位置,能够于执行期准备妥当。
拷贝构造函数
- 有三种情况,会以一个对象的内容作为另一个类对象的初值:
- 显式的以一个对象的内容初始化另一个对象
X xx = x;
- 当对象被当作参数交给某个函数时
void bar(X x){...}
- 函数返回一个类对象时
X aet() {X xx; return xx;}
- 显式的以一个对象的内容初始化另一个对象
- 如果类定义了拷贝构造函数,在大部分情况下,当一个类对象以另一个同类实例作为初值,这些拷贝构造函数会被调用。
- 如果类没有提供一个显式的拷贝构造,当类对象以一个相同类的另一个对象作为初值,其内部是以默认的成员逐次初始化完成的。
- 即把每一个内建的或派生的数据成员的值,从某个对象拷贝一份到另一个对象身上。不过它并不会拷贝其中的类对象成员,而是以递归的方式施行成员逐次初始化。
- 如果类没有声明一个拷贝构造,就会有隐式的声明或隐式的定义出现。而隐式的拷贝构造分为无用的和有用的,只有有用的才会被合成到程序当中。
- 判断一个拷贝构造是否为有用的的标准在于类是否展示出所谓的“位逐次拷贝”。
类不展示位逐次拷贝的4种情况:
- 当类内含一个对象成员,这个对象成员声明有一个拷贝构造时。
- 当类继承自一个基类,这个基类有一个拷贝构造函数(不论是被显式声明的还是合成而得的)时。
- 当类声明了一个或多个虚函数时。
- 当类派生自一个继承串链,其中有一个或多个虚基类时。
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Bkwin一12/01
- ♥ STL_stack05/19
- ♥ C++并发编程 _管理线程05/07
- ♥ C++_可以重载的运算符12/22
- ♥ 51CTO:Linux C++网络编程三08/16
- ♥ C++程序高级调试与优化_第一篇07/20