第1章关于对象

第1章关于对象

数据成员直接放在每一个类对象之中. 而成员函数虽在class的声明之内, 却不出现在对象之中. 每一个非内联成员函数值会诞生一个函数实例. 至于每一个"拥有零个或一个定义"的内联函数则会在其每一个使用者(模块)身上产生一个函数实例

  • virtual function机制, 用以支持一个有效的"执行期绑定"
  • virtual base class用以实现"多次出现在继承体系中的base class, 有一个单一而被共享的实例"

1.1 C++对象模型

C++中, 有两种数据成员: static和nonstatic, 以及三种类成员函数: static, nonstatic和virtual.

class Point {
public:
    Point(float xval);
    virtual ~Point();
    
    float x() const;
    static int PointCount();

protected:
    virtual ostream& print(ostream &os) const;
    float _x;
    static int _point_count;
};

简单对象模型: 成员本身并不放在对象之中. 只有"指向成员的指针"才放在object内. 这么做可以避免"members有不同的类型, 因而需要不同的存储空间"所招致的问题. 它可能是为了尽量减低C++编译器的设计复杂度而开发出来的, 赔上的则是空间和执行期的效率.

表格驱动对象模型: 为对所有类的对象都有一致的表达方式, 把所有与成员相关的信息抽取出来, 放在一个data member table和一个member functio table指针, 类对象本身则内含指向这两个表格的指针.

C++ 对象模型: 非静态数据成员被配置于每一个类对象之内, 静态数据成员则被存放在个别的类对象之外. 静态和非静态函数成员也被存放在个别的类对象之外, 虚函数则以两个步骤支持之:

  1. 每一个类产生出一堆指向虚函数的指针, 放在表格指针, 这个表格被称为虚函数表(virtual table, vtbal)
  2. 每一个类对象被安插一个指针, 指向相关的虚函数表. 通常这个指针被称为vptr. vptr的设定和重置都有每一个类的constructor, destructor和copy assignment运算符自动完成. 每一个类所关联的type_info object(用以支持runtime type identification, RNTI)也经由虚函数表指出, 通常放在表格的第一个slot

加上继承: C++支持单一继承, 多继承和虚继承(在虚拟继承的情况下, 基类不管在继承串链中被派生多少次, 永远只会存在一个实例, 例如iostream之中就只有virtual ios基类的一个实例)

派生类模塑其基类实例:

  1. "简单对象模型": 每一个基类可以被派生类实内的一个slot指出, 该slot含有基类对象的地址. 主要缺点是, 因为间接性而导致的空间和存取时间上的额外负担, 优点则是类对象的大小不会因其基类的改变而改变
  2. base class table: 这里所说的base class table被产生出来时, 表格中的每一个slot内含一个相关的基类地址, 这很像虚函数表内涵每一个虚函数的地址一样. 每一个类对象内含有一个bptr, 他会被初始化, 指向其base class table. 主要缺点是由于间接性而导致空间和存取时间上的额外负担, 优点则是在每一个类对象中对于继承都有一致的表现方式: 每一个类对象都应该在某个固定位置上安防一个base table(基类表?)指针, 与基类的大小或个数无关. 第二个有点事, 无须改变类对象本身, 就可以放大, 缩小, 或更改基类表

对象模型如何影响程序
不同的对象模型, 会导致"现有的程序代码必须修改"以及"必须加入新的程序代码"连个结果

X foobar() {
    X xx;
    X *px = new X;
    
    // foo()是一个virtual function
    xx.foo();
    px->foo();

    delete px;
    return xx;
};

这个函数有可能在内部被转化为

X foobar() {
    X xx;
    X *px = new X;
    
    // foo()是一个virtual function
    xx.foo();
    px->foo();

    delete px;
    return xx;
};

1.2 关键词所带来的差异

关键词困惑
两个node重定义

class node {};

struct node {};

报错

template<struct Type>
struct number {};

不报错

template<class Type>
struct numble {};

策略性正确的struct
待补充

1.3 对象的差异

C++程序设计模型直接支持三种程序设计规范(programming paradigms): 1. 程序模型; 2. 抽象数据类型模型; 3. 面向对象模型

虽然可以直接或简介处理继承体系中的一个基类对象, 但只有通过指针引用的间接处理, 才支持OO程序设计所需的多态性质

thing1的定义和运用逸出了OO的习惯, 它反应的是ADT paradigm的行为

Library_materials thing1;

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

// thing1不是一个Book!
// book被裁减
// 不过thing1仍保有一个Library_materials
thing1 = book;

// 调用的是Library_maternal::check_in()
thing1.check_in();

thing2的定义和语言, 是OO paradigm中的一个良好例证

// OK: 现在thing2参考到book
Library_maternal &thing2 = book;

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

在OO paradigm之中, 程序员需要处理一个位置实例, 它的类型虽然有所界定, 却有无穷可能. 这组类型受限于其继承体系, 然而该体系理论上没有深度和广度的限制. 在C++中, 只有通过指针和引用的操作来完成. 相反, 在ADT paradigm中, 程序员处理的是一个拥有固定而单一类型的实例, 它在编译期间就已经完全定义好了

// 描述objects: 不确定类型
Librar_maternal *px = retrieve_some_maternal();
Librar_maternal &rx = *px;

// 描述已知物: 不可能有令人惊讶的结果产生
Librar_maternal dx = *px

没有办法确定地说出px或tx到底指向何种类型对象, 只能确定要么是一个Librar_maternal类型要么就是Librar_maternal的子类型. dx可以确定是Librar_maternal类的一个对象

C++中指针或引用的处理不是多态的必要结果

// 没有多态, 因为操作对象不是class object
int *pi;

// 没有语言所支持的多态, 因为操作对象不是class object
void *pvi;

// ok: class x视为一个基类, 可以有多态效果
x *px;

一般而言类对象所需内存要有

  • 其非静态数据成员的总和大小
  • 加上由于alignment的需求而填补上去的空间(可能存在于成员之间, 也可能存在于集合体边界), 好像就是字节对齐的意思
  • 加上为了支持virtual而由内部产生的任何额外的负担

指针的类型

指针的大小都是一样的, 都是存储指向对象的地址, 区别仅仅是"指针类型", 他会知道编译器如何解释某个特定地址中的内存内容及大小, 即影响解释方式

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

ZooAnimal za("Zoey");
ZooAnimal *pza = &za;

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

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

她们每个都指向Bear对象的第一个byte. 期间的差别是, pb所涵盖的地址包含整个Bear对象, 而pz所涵盖的地址只包含Bear对象中的ZooAnimal, 除了ZooAnimal中出现的成员, 不能使用pz来直接处理Bear的任何成员, 可通过转换pz的类型间接实现. 唯一例外的是通过virtual机制

Bear b;
ZooAnimal za = b;   // 会引起切割
// 调用ZooAnimal::rotate()
za.rotate();

这里会引起两个问题: 1.为什么rotate()所调用的是ZooAnimal实例而不是Bear实例? 2.如果初始化函数讲一个对象内容完整拷贝到另一个对象去, 为什么za的vptr不指向Bear的virtual table

  1. za并不是(而且也绝不会是)一个Bear, 它是(并且只能是)一个ZooAnimal. 多态所造成的"一个以上的类型"的潜在力量, 并不能够实际发挥在"直接存储对象"这件事情上. 有一个似是而非的观念: OO程序设计并不支持对object的直接处理.
  2. 编译器在(1)初始化及(2)指定操作(将一个类对象指定给另一个类对象)之间做出了仲裁. 编译器必须确保如果某个object含有一个或一个以上的vptrs, 那些vptrs的内容不会被基类对象初始化或改变

猜你喜欢

转载自www.cnblogs.com/hesper/p/10588654.html