C++对象模型剖析(一)

C++对象模型

一、对象模型

  • 简单对象模型

    在简单对象模型中,class member并不存放在object中,在object中存放的是一个指向class member的指针;而class function则是存放一个指向function的函数指针

  • 表格驱动对象模型

    为了对所有class的所有object都有一致的对外表达方式,表格驱动对象模型将所有与member相关的信息抽离出来,放在一个data member table和一个member function table之中,class object本身则存放一个指向这两个表格的指针。

  • C++对象模型

    从简单对象模型派生而来,并对内存空间和存取时间做了优化。

    在该模型中,Nonstatic data members被配置于每一个class object之内, static data members则被存放在个别的class object之外;Static和nonstatic function members也被存放在个别的class object之外;Virtual function则以两个步骤支持:

    • 每个class产生出一堆指向vritual function的指针,放在表格中。这个表格被称为virtual tablevtbl
    • 每个class object被安插一个指针,指向相关的virtual table。通常这个指针被称为vptr。vptr的设定setting和重置resetting都由每一个class的constructor、desturctor和copy assignment运算符自动完成。每个class所关联的type_info object也经由virtual table被指出来,通常放在一个slot中。
    class Point {
    public:
        Point(float xval);
        virtual ~Point();
        
    protected:
        virtual ostream& print(ostream &os) const;
        
        float x;
        static int _Point_count;
    };
    

    在这里插入图片描述

继承

C++支持单继承,也支持多继承。

继承关系也可以被指定为虚拟继承virtual,共享的意思。在虚拟继承的情况下,base class不管在继承串中被派生derived多少次,永远只会存在一个实例(被称为subobject)。用在菱形继承中。

一个derived class在本质上模塑其base class实例的方法

  • 在简单对象模型中,每一个base class可以被derived class object内的一个slot指出,该slot内含base class subobject的地址。这个体制的主要缺点是,因为间接性而导致的空间和存取时间上的额外负担,优点则是class object的大小不会因其base class的改变而受到影响。
  • base table模型。base table被产生出来时,表格中的每一个slot内含一个相关的base table地址,这个跟virtual table内含每一个virtual function一样。每一个class object内含一个bptr,它们会被初始化,指向其base class table。这种策略的缺点是:由于间接性而导致的空间和存取时间上的额外负担,优点则是每一个class object中对于继承都有一致的表现方式:每一个class object都应该在某个固定位置上安放一个base table指针,与base class的大小或个数无关。第二个优点是,无需改变class objects本身,就可以放大、缩小、或更改base class table。
  • c++最初采用的继承模型并不运用任何间接性:base class subobject 的 data members 被直接放置于 derived class object中。这提供了对base class members最紧凑而且最有xiaol 的存取。但缺点是base class members的任何改变,包括增减、移除、改变类型等都使得所有用到该base class或其derived class的object必须重新编译。

对象模型如何影响程序

不同的对象模型会导致“现有的程序代码必须修改”或“必须加入新的程序代码”。

举个例子

// 在class X中定义了一个copy constructor,一个virtual destructor和一个virtual function foo:
X foobar() {
    X xx;
    X *px = new X;
    
    // foo() 是一个 virtual function
    xx.foo();
    px->foo();
    
    delete px;
    return xx;
}

// 这个函数有可能在内部被转换为
void foobar(X& _result)
{
    // 构造 _result
    // _result 用来取代local xx
    _result.X::x();
    
    // 拓展X *ptr = new X;
    px = _new(sizeof(X));
    if (px != 0) px->X::x();
    
    // 扩展xx.foo()但不使用virtual机制
    foo(&result);
    // 扩展px->foo()使用virtual机制
    (*px->vtbl[2])(px);
    
    // 扩展delete px;
    if (px != 0)
    {
        (*px->vtbl[1])(px);	// destructor
        _delete(px);
    }
    
    // 无需使用named return statement
    // 无需摧毁local object xx
    return;
}

对象的差异

c++程序设计模型直接支持三种programming paradigms程序设计范式。

  • 程序模型 procedural model
  • 抽象数据类型模型 abstract data type mode
  • 面向对象模型 object-oriented model

多态

Library_materials thing1;

// class Book : public Library_materials { ... }
Book book;

// thing1 并不是一个book
// book被裁切了sliced
// 但是thing1任然保留了Library_material
thing1 = book;

// 调用的是Library_materials
thing1.check_in();

Library_material& thing2;
thing2 = book;

// 引发的的是Book::check_in()
thing2.check_in();

随让可以直接或间接处理继承体系中的一个base class object,但是只有通过pointer或reference的间接处理,才支持OO程序设计所需的多态性质。

在OO paradigm之中,程序员需要处理一个未知实例,它的类型虽然有所界定,却有无穷可能。这组类型受限于其继承体系,然而该体系理论上没有深度和广度的限制。原则上被指定的object的真实类型在每一个特定执行点之前,是无法解析的。

在C++中,只有通过pointers和reference的操作才能够完成。相反地,在ADT paradigm中,程序员处理的是一个拥有固定而单一类型的实例,它在编译时期就已经完全定义好了。

C++以下列方法支持多态:

  • 经由一组隐式的转化操作。例如把一个derived class 指针转化为一个指向其public base type的指针。

    shape *ps = new circle()

  • 仅有virtual function机制

    ps->rotate() 意思是在基类中定义的virtual function在子类中重写,在通过基类指针指向不同的对象时,可以调用不同的方法实例

  • 经由dynamic_casttypeid运算符

    if ( circle *pc = dynamic_cast<circle*>(ps))...

多态的主要用途是经由一个共同的接口来影响类的封装,这个接口通常被定义为一个抽象的base class中。这个共享接口是以virtual function机制引发的,它可以在执行期根据object的真正类型解析出到底是哪一个函数实例被调用。

有关dynamic_casttypeid的介绍可以到这篇文章去详细了解一下。

[C++] - dynamic_cast介绍及工作原理、typeid、type_info_dynamic_cast原理-CSDN博客

  • 需要多少内存才能够表现一个class object?

    一般而言要有:

    • nonstatic data members的总和大小

    • 加上任何由于alignment的需求而填补padding上去的空间(可能存在于members之间,也可能存在于集合体边界)

      alignment就是将数值调整到某数的倍数。在32位的计算机上,通常alignment为4bytes,以使bus的“运输量”达到最高效率

      这句话的意思就是内存对齐

    • 加上为了支持virtual而由内部产生的任何额外负担overhead

指针的类型the type of a pointer

int *p1;
Array<string> *p2;
ZooAnimal *p3;

以内存需求的观点来说,上面三个指针并没有什么不同!它们都需要有足够的内存来放置一个机器地址。“指向不同类型的各指针”之间的差异,既不在其指针的表示法不同,也不在其内容(代表一个地址)不同,而是在其所寻址出来的object类型不同。也就是说,“指针类型”会教导编译器如何解释某个特定地址中的内存内容机器大小。

转换cast之影响”被指出的内存的大小和其内容“的解释方式。

class ZooAnimal {
public:
    ZooAnimal();
    virtual ~ZooAnimal();
    virtual void rotate();
protected:
    int loc;
    String name;
};

class Bear : public ZooAnimal {
public:
    Bear();
    ~Bear();
    void rotate();
    virtual void dance();
protected:
    enum Dances {...};
    
    Dances dances_known;
    int cell_block;
};

Bear b("Yogi");
Bear *pd = &b;
Bear &rb = *pd;

该类的布局

在这里插入图片描述

// 但是如果是通过这种方式来进行的话=
Bear b;
ZooAnimal *pz = &b;
Bear *pd = &b;

它们每个都指向Bear object的第一个bytes。其间的差别是,pd所涵盖的地址包含整个Bear object,而pz所涵盖的地址只包含Bear Object中的ZooAnimal subobject。

除了ZooAniamal subobject中出现的members,你不能够使用pz来直接处理Bear的任何members。唯一例外的是通过virtual机制。

z = &b;
Bear *pd = &b;

它们每个都指向Bear object的第一个bytes。其间的差别是,pd所涵盖的地址包含整个Bear object,而pz所涵盖的地址只包含Bear Object中的ZooAnimal subobject。

除了ZooAniamal subobject中出现的members,你不能够使用pz来直接处理Bear的任何members。唯一例外的是通过virtual机制。

引用文献:《深度探索C++对象模型》

  • 11
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值