深度探索C++对象模型-第五章

说明:

不是很清楚的点,用下划线。

解答,用斜体;

重点,用粗体加粗;

第五章 构造、析构、拷贝 语意学

5.1 纯虚函数

  • 拥有纯虚函数的类,为抽象类,不可能拥有实例(不可能创造出一个对象)。

    但若抽象类中有数据成员,则需要一个显式的构造函数去初始化它。

  • 不要把析构函数声明为 pure(纯)。

  • 不要给一个虚函数后面加 const。

5.2 “无继承”情况下的对象构造

当一个class导入一个虚函数时,会发生下列事情:

  1. 每一个class object多负担一个vptr;
  2. 自己定义的构造函数被附加了一些代码,实现vptr的初始化;
  3. 合成拷贝构造函数、赋值构造函数,因为vptr不能用默认的bitwise方式复制了;

5.3 继承体系下的对象构造

在继承下,编译器会扩充每一个constructor,扩充程度视继承体系而定。

constructor的调用伴随了哪些步骤?

  • 初始化列表(member initialization list)的data members初始化操作会被放进constructor的函数本身,并以members的声明顺序为顺序。(如*this->x = 0;)

  • 如果有一个member并没有在初始化列表中,但它在一个default constructor,那么该default constructor 必须被调用(手动)。

  • 在那之前,如果class object有virtual table pointer(s),它(们)必须被设定初始值,指定适当的virtual table(s)

  • 在那之前,所有上一层的base class constructors 必须被调用,以base class 的声明顺序为顺序(与初始化列表的顺序没有关联)。

    1. 如果base class 被列于初始化列表中,那么任何明确指定参数都应该传递过去。

    2. 如果base class 没有列于初始化列表,那么调用default constructor。

    3. 如果base class 是多重继承下的第二或后面的base class ,那么this指针必须有所调整。

  • 在那之前,所有 virtual base class constructors 必须被调用,从左到右,从最深到最浅。

    1. 如果class 被列于初始化列表中,那么如果有任何明确指定的参数,都应该传递过去,若没有列于初始化列表中,则调用default constructor。
    2. 此外,class中的每一个virtual base class subobject的偏移量必须在执行期可存取。
    3. 如果class object 是最底层的class,某constructors可能被调用;某些用以支持这个行为的机制必须被放进来。

【注】在那之前,是指在用户代码执行前。

其中的虚拟继承

为了防止重复对virtual base class调用构造函数,规定:只有在继承体系最深层的object才可以对virtual父类进行调用构造初始化。

其中的vptr语意学

vptr在constructor何时被初始化?

base class constructors调用操作之后,但是在程序员供应的码或是初始化列表中所列的members初始化操作之前。

5.4 对象复制语意学

在复制操作时,需要一个面对自我拷贝的过滤过程:

if( this == &rhs) return *this;

当设计一个class,并以一个class object 指定另一个class object时,有三种选择:

  1. 什么都不做,实施默认行为。

  2. 提供一个explicit copy assignment operator。

  3. 明确拒绝一个class object指定给另一个class object。

一个class对于默认的copy assignment operator,在以下情况下不会表现出 bitwise copy语意:

  1. 当一个class的 base class 有一个copy assignment operator时,

  2. 当一个class 的 member object,而其 class 有一个 copy assignment operator 时,

  3. 当一个class 声明了任何 virtual functions 时,

  4. 当class继承一个 virtual base class 时。但尽可能不要允许一个virtual base class的拷贝操作,也尽量不要在其中声明数据。

构造这样的一个继承体系:

class Base {
    public: virtual ~Base() {}
    virtual void show() { cout << "Base" << endl; }
};

class Derived : public Base {
    public: void show() { cout << "Derived" << endl; }
};

子类Derived类重写了基类Base中的show方法。 编写下面的测试代码:

Base b; 
Derived d;

b.show(); 
d.show();

结果是:

Base

Derived

Base的对象调用了Base的方法,而Derived的对象调用了Derived的方法。因为直接用对象来调用成员函数时不会开启多态机制,故编译器直接根据b和d各自的类型就可以确定调用哪个show函数了,也就是在这两句调用中,编译器为它们每一个都确定了一个唯一的入口地址。这实际上类似于一个重载多态,虽然这两个show函数拥有不同的作用域。 那这样呢: Base b; Derived d; b.show(); b = d; b.show(); 现在,一个Base的对象被赋值为子类Derived的对象。

那这样呢:

Base b; 
Derived d;

b.show(); 
b = d; 
b.show();

现在,一个Base的对象被赋值为子类Derived的对象。

结果是:

Base

Base

对于熟悉Java的人而言,这不可理解。但实际上,C++不是Java,它更像C。“b = d”的意思,并不是Java中的“让一个指向Base类的引用指向它的子类对象”,而是“把Base类的子类对象中的Base子对象分割出来,赋值给b”。所以,只要b的类型始终是Base,那么b.show()调用的永远都是Base类中的show函数;换句话说,编译器总是把Base中的那个show函数的入口地址作为b.show()的入口地址。这根本就没用上多态。

单继承下的重写多态

那我们再这样:

Base b; 
Derived d;
Base *p = &b;

p->show();
p = &d;
p->show();

这时,结果就对了:

Base

Derived

p是一个指向基类对象的指针,第一次它指向一个Base对象,p->show()调用了Base类的show函数;而第二次它指向了一个Derived对象,p->show()调用了Derived类的show函数。

总结:也就是说,只有是指针或者引用才是真正的多态,将子对象赋给父类对象其实类型向上转型

个人觉得C++容易弄混淆的地方(持续更新):

1.const和指针的修饰问题

const char * a; //一个指针a指向const char

char const *a; //这两个是a指向的内容是常量,不能改变

char * const a; //首先a 是指针然后还是const

const (char*) a; //这两个是a指针本身是常量,指针本身不能改变

其实,可以看出如果const修饰的char(也就是类型本身或者是 *variable对指针的解引用)就是指针指向的内容是常量,反之就是修饰指针本身的。那我们可以总结一个识别方法就是:看const 两边(当然有的只有一边)的类型是类型(指针指向的内容)就是类型变量本身是常量(如const char * a和char const a 的const两边是char,a)。

当然两者都是常量就是:const char * const a;第一个const是类型常量,第二个才是指针常量。同样给出 const char &a ;const char *a;在传递参数时使用。

2.数组和指针的组合问题

char * a[M]; 这是指针数组,就是每一个元素是指针的数组,每个元素都要初始化。a[M]一看就是数组,这个数组每一个元素是char *,所以可以将char *扩展为一维数组然后a[M]就是二维数组了。其实就是M个指针。

char (a)[N]; 这是一个指针,这个指针指向N个char元素,即指向数组的指针,其实就是一个指针。把(a)看着一个变量,这个变量是指向N个元素的指针,所以只是一个一维数组。把char (*a)[N]看成是char b[N]就可以了。

3.C++变量的初始化

对于内置类型局部变量不进行初始化,但是分配地址,全局变量会进行默认初始化。对于类类型局部变量(没有显式初始化)会进行默认初始化(有默认构造函数,否则报错),但其内部的内置数据成员不会进行初始化(如果在默认构造函数没有进行初始化)。数组也是同样。

参考文章

《深度探索C++对象模型(Inside The C++ Object Model )》学习笔记:https://dsqiu.iteye.com/blog/1669614

c++,为什么要引入虚拟继承 :https://www.cnblogs.com/mylinux/p/4725833.html

RTTI、虚函数和虚基类的实现方式、开销分析及使用指导:http://baiy.cn/doc/cpp/inside_rtti.htm

你可能感兴趣的:(深度探索C++对象模型-第五章)