[读书系列] 深度探索C++对象模型 初读

  2012年底~2014年初这段时间主要用C++做手游开发,时隔3年,重新拿起《深度探索C++对象模型》这本书,感觉生疏了很多,如果按前阵子的生疏度来说,现在不借助Visual Studio之类的工具的话,写C++代码感觉要比较费劲,最近重读了这本书后,感觉要写点东西下来(因为懒得用笔作笔记,太慢了-_-!)加深下印象。

  以前还是新手的时候,总是认为:

1.class如果没有定义任何constructor的话,编译器会自动合成一个default constructor(我习惯叫缺省的构造函数)出来

2.编译器合成出来的default constructor会为“class内的每一个data member“作初始化



  现在开始往不为新手所知的编译器合成的default constructor究竟做了什么神秘的事这个方向飞吧!:)

  《深度探索C++对象模型》说道编译器会在需要的时候自动合成default constructor,而“在需要的时候”指的有这样4种情况:

a1.class中带有member class object

a2.class的base class 带有default constructor

a3.class拥有(继承)一个或者多个virtual function

a4.带有一个virtual base class

a1的情况:

class A
{
public:
  A() {}
};
class B : A
{
private:
  A a;
  int count;
};
void Test()
{
  B b;//B::a必须在初始化
}

//此时编译器会为class B 自动生成一个default constructor,然后其中负责了class A 对象a的初始化工作
//生成的构造函数伪代码可能像下面这样:
class B
{
public:
  B()
  {
    //编译器生成的,且这段代码是在用户代码之前的
    this.a.A::A();

    //但是count是class B 对编译器来说不是必要的(对用户来说是必要的),所以count的初始化工作时是需要用户自己负责的
    //如this.count=0;
  }
};

当然,如果class B中组合有多个其他的class,且它们都像class A一样带有default constructor,那么编译器会以class B中声明它们的顺序,依序自动生成每个class对应的default constructor,比如:

class B {public: B() {}};
class C {public: C() {}};
class D {private: A a; B b; C c;};
void Test()
{
  D d;//会在class D中自动生成default constructor
}
//如下自动生成的default constructor伪代码
class D
{
public:
  D()
  {
    a.A::A();
    b.B::B();
    c.C::C();
  }
};

a2的情况:和1的类似,编译会为所有带default constructor的class 的derived class 生成需要的default constructor,用于初始化base class,当然这些代码会安插在用户初始化自己的data member的代码之前!!如果同时存在带default constructor的 member class objects,那么会在上述的初始化base class的default constructor之后生成

a3.的情况:

//直接摘抄《深度探索C++对象模型》的示例
class Widget
{
public:
  virtual void flip() = 0;
};
void flip(const Widget& widget) { widget.flip(); }

//假设Bell 和Whistle都派生自Widget
void foo()
{
  Bell b;
  Whistle w;
  flip(b);
  flip(w);
}

在这种情况下,在编译期间,会有这样的行为:

1.一个virtual funciton table被生成出来,里面存放着虚函数的地址(当然还有type_info类型信息);

2.为每个class object合成一个vptr(virtual pointer,指向虚表的指针),期内存放着相关class的vtbl(上述的virtual function table)的地址(ps:相关class可指的是base class中的虚表)

因此上述的widget.flip()的调用可能是这样的决议:(*widget.vptr[1])(&widget), (ps:这里假定Widget只有1个虚函数,vptr[1]指的就是存放指向虚函数flip()的地址,而vptr[0]指的是类型信息), &widget指的是Widget类的某一个实例交付给flip的this指针。

为了使多态这个机制发挥它应有的作用,编译器会为Widget的每个object(或者其派生类object)合成vptr,存放适当的指向vtbl(virtual function table)的地址,因此对于那些没有声明任何constructor的class,编译器这个时候就为它们自动生成了defalut constructor来做vptr做初始化的工作!

a4的情况:

//摘自《深度探索C++对象模型》
class X { public: int i; };
class A : public virtual X { public: int j; };
class B : public virtual X { public: int k; };
class C : public A, public B { public: int d; }

void foo(const A* pa) { pa->i = 1024; }

mian()
{
    foo(new A);
    foo(new B);
}

继承关系如右图: 

C中的内存布局如右图:

编译器无法在编译期决议出pa->X::i的位置,因为pa的类型是可变的,可能是A或C或X类型,因此也就无法确切的知道X::i在内存中的偏移位置,这时编译器对该行代码可能的转变操作有

pa->__pacX->i = 1024,而这个__pacX正是编译器安插的指向base class的指针,在运行期才可正确决议如何存取X::i,这个操作在构造期间完成,如果用户没有声明任何constructor,那么这时编译器会合成default constructor来完成这个操作,安插对base calss进行存取操作的代码!



至此看回前面的两个观点:

1.任何class如果没有定义default constructor(我习惯叫默认的构造函数)的话,编译器会自动合成一个出来

2.编译器合成出来的default constructor会为“class内的每一个data member“作初始化

没有1个是正确的!-_-!!!当然你可以说本来就是这样,但相信很多新手肯定和我最开始的时候一样抱着这样的想法!!!



下面 补上C++对象模型的结构图:来自网络 ,顺便大致说说:

图中对应的代码大致如下:

class Point {
public:
    Point(float xval) {
        this.x = xval;
    }

    virtual ~Point(){
        //...
    }

    float x() const {
        return this._x;
    }

    static int PointCount(){
        //...
        return 0;
    }
protected:
    virtual ostream& print(ostream&) const {
        //...
    }

    float _x;
    static int _point_count;
};

大致的理解如下:

1.编译器为每个Point类对象pt安插一个虚表指针_vptr_Point,指向虚表Virtual table for Point
2.上述虚表中0索引位置存放type_info类型信息地址,1索引位置存放虚析构函数地址,2索引位置存放虚函数print地址
3.nonstatic data members像虚表指针一样,存在于每个class object之内(亦即类的每个实例都有着自己的数据)
4.staitc data members 和 nonstatic或者static function members 存在于class object之外
基于上述说明,现在我不会再像以前迷惑于为什么对有些类(比如内含有虚函数)取sizeof时得到的结果会大于自己的猜想,因为类的vptr虚表指针也需要占据内存(32位机器占用
4个字节),如果是属于继承链中的一环,那还要算上其base class中的vptr开销!!
再进一步的,可以深入了解对象的(数据)内存布局了!!希望我们不会再为constuctor背后做的事所迷惘,冲过这令人眩晕的“暗涌”吧!!!

时间: 2024-12-06 05:07:36

[读书系列] 深度探索C++对象模型 初读的相关文章

深度探索C++对象模型的读书心得

参考:深度探索C++对象模型 (stanley B.Lippman著 侯捷翻译) 1. Page9 : C++对象模型,说明每一个类仅有一个虚函数表Vtbl,而类的每一个对象都有指向其表的指针. 2. Page30:引用也需要与一个指针(大小为4BYTE)相同的空间. 3. Page28: 指针类型会教导编译器如何解释某个特定地址中的内存内容及大小. 4. Page39: explicit关键字能够制止"单一参数的constructor被当做一个Conversion运算符" 5. Pa

柔性数组-读《深度探索C++对象模型》有感 (转载)

最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下: 例如,把单一元素的数组放在一个struct的尾端,于是每个struct objects可以拥有可变大小的数组.    code: struct mumble { //stuff char pc[1];    };        //从档案或标准输入装置中取得一个字符串,然后为struct本身和该字符串配置

【深度探索C++对象模型】第一章 关于对象

第一章 关于对象(Object Lessons) -- 本书作者:Stanley B.Lippman 一.前言 什么是 C++ 对象模型:简单的说,就是 C++ 中面向对象的底层实现机制. 本书组织: 第 1 章,关于对象(Object Lessons),介绍 C++ 对象的基础概念,给读者一个粗略的了解. 第 2 章,构造函数语意学(The Semantics of Constructors),构造函数什么时候会被编译器合成?它给我们的程序效率带来了怎样的影响? 第 3 章,Data语意学(T

【深度探索C++对象模型】第二章 构造函数语意学(上)

第二章 构造函数语意学(The Semantics of Constructors) -- 本书作者:Stanley B.Lippman 一.前言 首先让我们来梳理一个概念: 默认构造函数(Default Constructor) : 是在没有显示提供初始化式时调用的构造函数.它由不带任何参数的构造函数,或是为所有形参提供默认实参的构造函数定义.如果定义的某个类的成员变量没有提供显示的初始化式时,就会调用默认构造函数(Default Contructor). 如果用户的类里面,没有显示的定义任何

《深度探索c++对象模型》chapter1关于对象对象模型

在c++中,有2种class data member:static和nostatic,以及3钟class member function:static,nostatic和virtual.已知下面这个class Point声明: class Point { public: Point(float xval); virtual ~Point(); float x() const; static int PointCount(); protected: virtual ostream& print(o

深度探索C++对象模型第6章 执行期语意学

(一)对象的构造和析构(Object Construction and Destruction) 一般而言我们会把object尽可能放置在使用它的那个程序区段附近,这么做可以节省非必要的对象产生操作和摧毁操作. 全局对象 如果我们有以下程序片段: Matrix identity main() { //identity 必须在此处被初始化 Matrix m1=identity; ... return 0; } C++保证,一定会在main()函数中第一次用到identity之前,把identity

【深度探索c++对象模型】Function语义学之虚函数

虚函数的一般实现模型:每一个class有一个virtual table,内含该class之中有作用的virtual function的地址,然后每个object有一个vptr,指向virtual table. 识别class是否支持多态,唯一恰当的方法是看它是否有virtual function,只要class拥有virtual function,它就需要额外的执行期信息. 考虑ptr->z(),ptr是基类指针,z是虚函数,为了找到并调用z()的适当实体,我们需要两项信息: 1.ptr所指对象

《深度探索c++对象模型》chapter3 Data语意学

一个空的class:如 class X{} ; sizeof(X)==1; sizeof为什么为1,他有一个隐晦的1 byte,那是被编译器安插进去的一个char,这使得class2的两个objects得以在内存中配置独一无二的地址: X a,b; if(&a==&b) cerr<<"yipes!"<<endl; class X{}; class Y:public virtual X{}; class Z:public virtual X{};

深度探索C++对象模型

著:Stanley B. Lippman  侯捷译  Inside the C++ Object Model 第一章:关于对象(Object Lessons) C++对象模型,了解到也是一个演变的过程,C++对象中包含了以下内容:(data members) non-static data,static data(const),以及(member functions):static,non-static,virtual 发展的主要过程主要集中于如何存储data 和 function,寻求时间和空