0%

第一章 关于对象

  • C语言中,数据和函数是分开来声明的,也就是说,语言本身没有支持”数据和函数“之间的关联性,这种程序方法称为程序性的procedural)。一般从C
    struct转到C++的封装类后,唯一在布局以及存取时间上的负担是virtual引起的。

1.1 C++对象模式(The C++ Object Model)

  • C++中有两种class data members:static和nonstatic,以及三种class member functions:static、nonstatic和virtual。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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;
    };
简单对象模型(A Simple Object Model)
1
2
graph LR
slot[Point pt] ==> m[all members]
  • 这个模型简单,一个object是一系列的slots,members按照声明次序各被指定一个slot。一个class object的大小是”指针大小*members个数“。
    • 这个模型应用到C++的”指向成员的指针“(pointer-to-member)观念中。
表格驱动对象模型(A Table-driven Object Model)
1
2
3
4
5
graph LR
S[Point pt] == data member table ==> e[内含实际数据]
S == member function table ==> m[内含函数地址]
m ==> a[...]
m ==> b[...]
  • 这种模型把所有与members相关的信息抽出来,放在一个data member table和一个member function table之中,class object则包含指向这两个表格的指针。member function table是一系列slots,一个slot一个member function;data member table则直接含有data本身。
  • 这个模型member function table这个观念称为支持virtual functions的一个有效方案
C++对象模型(The C++ Object Model)
1
2
3
4
5
6
7
8
graph LR
e1(static data members)
e2(static function members)
e3(nonstatic function members)
Point[nonstatic data members] == Virtual table for Point ==> vptr[vtbl]
vptr == RTTI ==> ti[type_info for Point]
vptr ==> a[...]
vptr ==> b[...]
  • nonstatic data members被配置于每个class object之内,static data members则被存放在class object之外;
  • static和nonstatic function members也放在所有class object之外;
  • virtual functions以两个步骤支持它:
    • 每个class产生一堆指向virtual functions的指针,放在virtual tablevtbl)中;
    • 每个class object被添加一个指针,指向vtbl,通常这个指针被称为vptr。vptr的设定(setting)和重置(resetting)由每个class的constructor、destructor和copy assignment运算符自动完成。每个class所关联的type_info object(用以支持runtime type identificationRTTI)也经由vtbl指出,通常放在表格第一个slot处。
  • 这个模型优点在于空间和存取时间的效率;缺点则是如果所用到的class objects的nonstatic data members有所改动,那么那些应用程序得重新编译。
加上继承(Adding Inheritance)
  • C++支持单一继承多继承虚拟继承
    1
    2
    3
    4
    5
    graph BT
    ostream --- ios
    istream --- ios
    iostream --- ostream
    iostream --- istream
对象模型如何影响程序
  • 下面这个函数,其中class X定义一个coyp constructor,一个virtual destructor和一个virtual function foo:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    X foobar()
    {
    X xx;
    X *px = new X;
    // foo() is a virtual function
    xx.foo();
    px->foo();
    delete px;
    return xx;
    }
    // 这个函数可能在内部转化为:
    // px->_vtbl[0] --> type_info object
    // px->_vtbl[1] --> X::~X()
    // px->_vtbl[2] --> X::foo()
    void foobar(X &_result)
    {
    _result.X::X();
    // X *px = new X;
    px = _new(sizeof(x));
    if (px != 0)
    px->X::X();
    // xx.foo(),不适用virtual机制
    foo(&_result);
    // virtaul机制扩展px->foo()
    (*px->vtbl[2])(px)
    // delete px
    if (px != 0)
    {
    (*px->vtbl[1])(px); // destructor
    _delete(px);
    }
    // 无须使用named return
    statement
    // 无须摧毁local object xx
    return;
    }

1.2 关键词所带来的差异(A Keyword Distinction)

关键词的困扰
  • 关键词struct的意思是一个数据集合体,没有private data,也没有data的相应操作。它和C++的使用者自定义类型user-defined type)区分开来。struct关键词的使用实现了C的数据抽象观念,而class关键词实现的是C++的ADTAbstract Data Type)观念。
策略性正确的struct(The Politically Correct Struct)
  • C的巧计有时候是C++的陷阱。例如一个在struct尾端的单一数组,也是每个struct objects有可变长数据:
    1
    2
    3
    4
    5
    6
    7
    struct mutable {
    /* stuff */
    char pc[1];
    };
    // 为struct本身和该字符串配置足够的内存
    struct mumble *pmumb1 = (struct memble*)malloc(sizeof(struct mumble) + strlen(string) + 1);
    strcpy(&mumble.pc, string);
    让C++ class的部分数据拥有C声明那模样,是将那部分抽取出来成为独立的struct声明,然后组合在一起,它能保证拥有与C兼容的空间布局:
    1
    2
    3
    4
    5
    6
    7
    struct C_Point { ... };
    class Point {
    public:
    operator C_point() { return _c_point; }
    private:
    C_point _c_point;
    };

1.3 对象的差异(An Object Distinction)

  • C++有以下方法支持多态
    • 隐式转化操作。(Shape *ps = new Circle()
    • 经由virtual function机制。(ps->rotate();
    • 经由dynamic_cast和typeid操作符。(if(Circle \*pc = dynamic\_cast<Circle\*>(ps))
  • 需要多少内存才能够表现一个class object大小
    • nonstatic data members的总和大小
    • 根据alignment需求padding的空间。(alignment是将数值调整到某数的倍数。32位机器上位4 bytes,64位机器上位8bytes)。
    • 支持virtual而内部产生的额外负担(overhead)
指针的类型(The Type of a Pointer)
  • 一个指向ZooAnimal的指针与一个指向整数的指针或template Array的指针在内存上没什么区别。真正的不同是其所寻址出来的object类型不同,因为指针类型会教导编译器如何解释某个地址中的内存内容及其大小。所以,这就是为什么void*能持有一个地址,但不同操作该地址的原因了。而转换cast)也是一种编译器指令,它不改变地址,只影响内存大小和内容的解释方式
加上多态之后(Adding Polymorphism)
  • 定义一个Bear,作为一种ZooAnimal:
    1
    2
    graph TD
    ZooAnimal --- Bear
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Bear : public ZooAnimal {
    public:
    Bear();
    ~Bear();
    // ...
    void Rotate();
    virtual void dance();
    // ...
    protected:
    enum Dances { ... };
    Dances dances_known;
    int cell_block;
    };

    Bear b;
    ZooAnimal *pz = &b;
    Bear *pb = &b;
    两个对象都同指Bear object的第一个byte。区别是pb的地址包含Bear object,而pz的地址包含Bear Object中的ZooAnimal subobject。除非ZooAnimal subobject出现的members,否则不能使用pz直接处理Bear的members,唯一例外是virtual机制:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // error. cell_block不是ZooAnimal的member
    pz->cell_block;
    // ok
    (static_cast<Bear*>(pz))->cell_block;
    // better
    if (Bear *pb2 = dynamic_cast<Bear*>(pz))
    pb2->cell_block;
    // ok
    pb->cell_block
    当写下pz->rotate();时,pz的类型在编译期决定了两点:
    • 固定的可用接口。就是说,pz只能调用ZooAnimal的public接口
    • 该接口的access level(例如ratate()时ZooAnimal的public member)。
      每一个执行点,pz指的object类型决定rotate()调用的实例。类型信息的封装维护于object vptr和vtbl之间的link之中。
  • 一个pointer或一个reference之所以支持多态,是因为它们不引发内存中任何于类型有关的内存委托操作(type-dependent commitment),会受到改变的只有指向内存的大小和内容解释方式而已。当一个base class object被直接初始化(或被指定为)一个derived class object时,derived object就会被切割sliced)以塞入较小的base type内存中,derived type将没有留下任何蛛丝马迹,因为配置所得的内存有限。
  • C++支持具体的ADT程序风格,被称为object-basedOB)。一个OB设计可能比一个对等oo设计速度更快而且空间更紧凑,因为所有函数调用操作在编译时期解析完成,对象构建起来不再设置virtual机制,而且空间紧凑,缺点是OB设计比较没有弹性。