码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2017-07-28 13:26:41      阅读:277      评论:0      收藏:0      [点我收藏+]

标签:指针   south   c++   prot   rtu   val   luci   用途   复制   

第一章 关于对象(Object Lessons)
—— 本书作者:Stanley B.Lippman
 
一、前言
    什么是 C++ 对象模型:简单的说。就是 C++ 中面向对象的底层实现机制。

    本书组织:
    第 1 章,关于对象(Object Lessons),介绍 C++ 对象的基础概念,给读者一个粗略的了解。
    第 2 章,构造函数语意学(The Semantics of Constructors),构造函数什么时候会被编译器合成?它给我们的程序效率带来了如何的影响?
    第 3 章。Data语意学(The Semantics of Data),讨论 data members 的处理。
    第 4 章,Function语意学(The Semantics of Function),讨论类的各种成员函数,特别是 Virtual 。

    第 5 章,构造、析构、拷贝语意学(Semantics of Construction, Destruction, and Copy),探讨怎样支持 class 对象模型,以及 object 的生命周期。

    第 6 章,运行期语意学(Runtime Semantics),暂时对象的生与死,new 与 delete 的支持。

    第 7 章,在对象模型的顶端(On the Cusp of the Object Model),专注于 exception handling, template support, runtime type identification(RTTI)。
    读完此书,或者此系列blogs,会让你对 C++ 的 class 有更深的了解。你将知道虚函数的实现方式,以及它所带来的负担。等等等等,这里有你想知道关于 class 的一切。

    
    在 C 语言中,“数据”和“处理数据的操作(函数)”是分开来声明的。也就是说,语言本身并没有支持“数据和函数”之间的关联性。

我们把这样的程序方法称为“程序性的”。比如。我们声明一个 struct Point3d:

typedef struct _Point3d
{
    float x;
    float y;
    float z;
} Point3d;
    欲打印一个 Point3D,我们可能须要这样一个函数:
void Point3d_print( const Point3d* pd )
{
    printf("(%g, %g, %g)", pd->x, pd->y, pd->z);
}
//%g和%G是实数的输出格式符号。它是自己主动选择%f和%e两种格式中较短的格式输出。而且不输出数字后面没有意义的零。
    在 C++ 中,你可能会这样来设计一个双层或者三层的Point3D:
class Point 
{
public:
    Point( float x = 0.0 ) : _x(x) {}

    float x() { return _x; }
    void x( float val ) { _x = val; }
    // ...
protected:
    float _x;
};

class Point2d : public Point
{
public:
    Point2d( float x = 0.0, float y = 0.0 ) : Point( x ), _y( y ) {}
    // ...
protected:
    float _y;
}

class Point3d : public Point2d
{
public:
    Point3d( float x = 0.0, float y = 0.0, float z = 0.0 ) : Point2d( x, y ), _z( z ) {}
    // ...
protected:
    float _z;
}
    从软件project的眼光来看,面向对象的特征。使得 C++ 比 C 看起来似乎更好。C 相对而言,更精瘦和简易。C++ 看起来似乎更复杂,但并不意味着 C++ 不更有威力。
    当一个 Point3d 转换到 C++ 之后,第一个可能会问的问题是:加上了封装之后,布局成本添加了多少呢?答案是: class Point3d 并没有添加成本。

三个 data members 直接内涵在每个 class Object 之中。而 成员函数(member functions)尽管在 class 的声明之内,但却不会出如今 class 的对象实体(Object)中。每个非 inline member function 仅仅会诞生一个函数实体。而 inline function。会在其每个使用者身上产生一个函数实体。

后面你将看到,C++ 在布局和存取时间上基本的负担 是由 virtual 引起的。包含 虚函数 以及 虚基类。

 
二、C++ 的对象模型
    首先,C++ 中。
    2种成员变量(class data members):静态的(static) 和 非静态的(non-static);
    3种成员函数(class member functions):静态的、非静态的 和 虚拟的(virtual)。
    我们来看这么一个类:
class Point 
{
public:
    Point( float valx );
    virtual ~Point();
    
    float x() const;
    static int PointCount();

protected:
    virtual ostream& print( ostream &os ) const;

    float _x;
    static int _point_count;
};
    那这个 class Point 在机器中将会被怎么表示呢?这有没有引起你的求知欲?
    【注】原书这里介绍了 简单对象模型  表格驱动的对象模型 。这里跳过这两个,直接看 C++ 对象模型。
    在 C++ 对象模型中。
    非静态的(non-static)成员变量 被配置于每个 class object 之内;
    静态的(static)成员变量 则被存放在全部 class object 之外。也就是全局数据区。(问:假设是这样。我们的 class 怎么样去全局数据区找到属于它的 static 成员变量?别急,后面会有答案)。
    静态和非静态的成员函数,也被配置于 每个 class 的实体之外。
    虚函数的配置方法是:
        1. 每个 class 产生出一堆指向 virtual functions 的指针,并把这些指针放在表格之中。这个表。既是所谓的 虚函数表(virtual table), 或 vtbl
        2. 每个 class 的实体(object) 被加入了一个指针。指向相关的(注意不一定是同一个) virtual table。

通常这个指针被称为 vptr

vptr 的设定和重置都有每个 class 的 构造函数、析构函数、拷贝以及复制运算符。每个 class 所关联的 type_info object( 用以支持 runtime type identification, RTTI )也经由 virtual table 被指出来,一般是放在表格的第一个 slot 处。

技术分享
 
三、C++ 怎样支持多态
    1. 经由一组隐含的转化操作。比如。把一个 派生类 的指针转化为一个指向其 public base type 的指针:
        shape* ps = new circle();
    2. 经由 virtual functions 机制:
        ps->rotate();
    3. 经由 dynamic_cast 和 typeid 运算符:
        if ( circle *pc = dynamic_cast< circle* >(ps) )...
    多态的主要用途。是经由一个共同的接口。来影响类型的封装,我们一般会把这个接口定义在一个抽象基类里面。然后再在派生类里重写这个接口。

四、须要多少内存来表现一个 class object?
    猜想以下的代码的 sizeof 结果会是?
    .eg.1.
class Base
{
public:
    Base();
    ~Base();
};
// sizeof(Base) = ?
    .eg.2.
class Base
{
public:
    Base();
    ~Base();

protected:
    double m_Double;
    int m_Int;
    char m_BaseName;
};
// sizeof(Base) = ?
    到底须要多少内存,才干表现一个 class 的 object 呢?一般而言有:
    1. 其 非静态的成员变量( non-static data members ) 的总和大小。
    2. 加上不论什么因为 内存对齐 的需求而填补上去的控件。
    3. 加上为了支持 virtual 而由内部产生的不论什么额外的负担。
    此外,须要注意的是。一个指针(或是一个 reference)。无论它仅仅想哪一种数据类型。指针本身所需内存大小是固定的。比方。在 win32下,一个指针的大小就是4个字节(byte)。
    问题的答案:
第一题: 答案是1。
class Base 里仅仅有构造函数和析构函数,由前面的内容所知,class 的 member functions 并不存放在 class 以及事实上例内,因此,sizeof(Base) = 1。是的,结构不是0,而是1,原因是由于,class 的不同实例,在内存中的地址各不同样,一个字节仅仅是作为占位符,用来给不同的实例分配不同的内存地址的。
第二题:答案是16。
double 类型的变量占用8个字节。int 占了4个字节,char 仅仅占一个字节。但这里它会按 int 进行对齐,Base 内部会填补3个字节的内存大小。

最后的大小就是 8 + 4 + 1 + 3 = 16。

大家能够调整三个成员变量的位置,看看结果会有什么不同。
 
五、指针的类型
Base* p_Base;
int*    p_Int;
vector<string> * p_vs;
    请问,一个指向 Base class 的指针和一个指向 int 的指针是怎样产生不同的呢?
    1. 以内存需求的观点来说,没有不同。在32位机器上,它们都须要4个字节的内存空间。
    2. “指向不同内存的各指针”间的差异。在于其所寻址出来的 object 的类型不同。

    也就是说,“指针类型”会教导编译器怎样解释某个特定地址中的内存内容及其涵盖大小。

比方:一个指向 int 的指针,如果其地址是 1000。在32位及其上。将涵盖地址空间 1000~1003.

    那么。一个指向地址 1000 的 void* 的指针,将涵盖如何的地址空间呢?没错,我们并不知道。这就是为什么一个类型为 void* 的指针。仅仅可以含有一个地址,而不可以通过它操作所指的 object 的缘故。
    所以。转型(cast)事实上是一种编译器指令,它所做的,并非改变指针所含的真正地址,而是教导编译器该去怎样解释指针所涵盖的地址空间。
 
六、小结
    第一章——关于对象。本章初步介绍了C++的对象模型是如何的,后面的章节将继续讨论这个对象模型的底层实现机制。
    在读完本篇文章之后。你应该理解:
  • 怎样计算 sizeof(classA) 的大小;
  • 了解 class 的内存布局。    
    在下一章——构造函数语意学中。我们将了解关于类的构造函数的很多其它知识。

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

标签:指针   south   c++   prot   rtu   val   luci   用途   复制   

原文地址:http://www.cnblogs.com/jzdwajue/p/7249651.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!