现在的位置: 首页 > 综合 > 正文

C/C++的对象模型

2012年09月11日 ⁄ 综合 ⁄ 共 3642字 ⁄ 字号 评论关闭

一、C/C++对象模型的概念

 

        这里所说的对象模型并非指有些人可能会误以为的Lippman在《Inside The C++ Object Model》一书中所描述的对象模型,Lippman描述的是面向对象的底层实现机制,但下面要讨论的对象模型是语言层面上的数据抽象的框架,两者是不同的,面向对象的语言设施建立在对象模型之上。在程序语言设计理论中,其实并没有对象的概念,只有变量概念,但与C/C++的变量又不尽相同,相比之下,与之更加类似的反而是C/C++的对象模型。

        与内存模型类似,C只提出了对象的单独定义,而C++则提出了整体概念。两者大多数基本语义是相同的,都是存储中的一段区域,但函数不是对象,即对象是数据抽象而不是操作抽象,函数才是操作抽象。

 

二、完整对象和子对象

 

        一个对象可以由其它对象组成,包含其中的对象称为子对象(subobject)。C中子对象的例子是数组元素和结构成员,而C++比C复杂一些,除了数组元素和结构成员,还包括基类子对象和成员子对象。相对于子对象,一个对象如果不是任何其它对象的子对象,它就称为完整对象(complete object),当我们说一个对象的完整对象的时候,它包含两方面的含义:

1. 如果这个对象是完整对象,那么这个对象的完整对象就是它本身;

2. 如果这个对象是其它对象的子对象,那么这个对象的完整对象就是包含它的那个对象。

例如:

 

struct A { int i; };

struct B : A { int j; };

A a;

B b;

 

b的完整对象就是b本身,但b.j的完整对象不是j,而是b,a的完整对象也依然是a,这一点有些人可能会觉得惊讶,虽然A和B之间有继承关系,但完整对象与子对象的划分是基于实例的,b中的A类对象才是b的子对象,但b不是a的完整对象。

 

三、最终派生对象

 

        为了与基类子对象进行区分,一个具有最终派生类(most derived class)类型的对象称为最终派生对象(most derived object),何谓最终派生类类型?最终派生类类型指的是具有类类型的完整对象、数据成员或者数组元素的类型,但不包括内置数据类型的派生关系。例如:

 

struct A { int i; };

struct B : A { int j; };

struct C { A k; };

A a;

B b;

C c;

B d[ 10 ];

 

a和b都是最终派生对象,但b中的A类子对象不是最终派生对象,d每一个子对象都是最终派生对象,c.k是最终派生对象,但a.i和a.j不是最终派生对象。

        一个最终派生对象的二进制宽度不能为0,至少具有一个或多个字节的存储,基类子对象才允许0二进制宽度。例如:

 

struct A { };

struct B : A { };

struct C : A { A i; };

A a;

B b;

C c;

 

虽然A没有任何数据成员,但由于a是最终派生对象,因此sizeof( a )不能为0,但b和c中的A基类子对象允许为0;c.i由于是最终派生对象,因此sizeof( c.i )不能为0,从而导致sizeof( c )也不能为0。

        最终派生对象概念用于解决空类实例化所产生的地址唯一性问题,最终派生对象的地址总是唯一的。

 

四、对象的属性

 

        除了上述完整对象、子对象和最终派对象的关系外,对象还具有很多属性,分为名字、地址、值、类型和生命期等等。

 

1.  对象的名字

 

        对象具有名字属性,但并不是所有对象都有名字。对象声明将对象与一个名字关联起来,此时对象也叫做变量(variable),对象名字也称为变量名,这里关于变量和变量名的描述是C++的,C标准虽然在上下文中到处使用着变量这个名词,却没有对变量定义提出明确的规定,这个行为让人不得不感到莫名其妙,类似的例子还有实体(entity)。

        无名对象也叫匿名对象,匿名对象可以由声明、表达式的结果或中间结果产生,但C中的匿名对象声明是未定义行为(匿名位域除外),C++则允许匿名联合声明。表达式计算所产生的匿名对象也叫临时对象,有些人将临时对象称为临时变量,这是错误的,因为变量是一段命名的存储,它必定具有名字,不存在无名变量。

 

2.  对象的地址

 

        对象的地址指示出对象在存储中的位置。但并非所有对象都是可寻址的,例如最小的对象位域,由于它的大小可以小于一个byte,因此位域是不能进行取地址运算的。基于同样的原因,位域也不能作为完整对象出现,只能作为子对象。

 

3.  对象的值

 

        对象的值就是对象所包含的内容,但对象的内容并不总能视为一个值,例如聚集和类的内容就不能视为值,只有标量类型的对象的内容才能视为值,在C/C++中,标量类型指的是数值类型和指针类型。

 

4.  对象的存储持续性和生命期

 

        对象具有存储持续性,C/C++将对象的存储持续性分为三类:静态、自动和动态。具有外部或内部链接性或被static关键字修饰的对象具有静态存储周期,生命期从程序开始到程序结束;无链接性及无static修饰的对象具有自动存储周期,由编译器自动管理该对象的创建和销毁;具有动态存储周期的对象由malloc()、realloc()、calloc()或new创建,并由程序员自己负责销毁。

        对象生命期是最容易与对象存储持续性混淆的概念,对象生命期指的是对象从创建到销毁这个过程的时间持续性,存储持续性虽然很大程度上决定了对象的生存方式,但本质上其实是对象的存储管理方式。

 

5.  对象的类型

 

        对象类型是对象最重要的属性,它决定了对象的种类和大小。从常识上讲,似乎对象类型属于对象属性是天经地义的事情。对于C++,的确如此,C++认为对象类型是与生俱来的,是在对象创建的那一刻就存在的,但对象的值可以由访问该对象的表达式的类型进行解释,即同一个对象的值可以具有不同的数值表示;但是,C的观念与上述情形却恰恰相反,C认为类型不是对象的属性,无论对象本身还是内部都不存在类型信息,C标准在rational中指出:

 

3. Terms and definitions

 

The definition of object does not employ the notion of type. Thus an object has no type in and of itself. However, since an object may only be designated by an
lvalue (see §6.3.2.1), the phrase “the type of an object” is taken to mean, here and in the Standard, “the type of the lvalue designating this object,” and “the value of an object” means “the contents of the object interpreted as a value of the type
of the lvalue designating the object.”

 

在C中,对象的类型是由引用该对象的左值表达式决定的,而非对象本身,当我们说一个对象具有某个特定类型时,指的是引用该对象的左值表达式的类型。

        关于对象类型的观念的巨大差异导致C和C++对于对象的值产生了不同的解释,请看下面的代码:

 

int i = 0x01010101;        /*假设int为32位*/

short *p = ( short* )&i;   /*假设short为16位*/

 

在C中,*p和i被认为引用了两个不同类型的对象,且具有不同的值;而对于C++,*p和i引用了相同的对象,但从*p和i看来其值不一样。尽管底层解释不同,但从引用该对象的表达式的角度来看,效果是一样的。

        还有另一个问题,通过什么方式访问对象的内容?,C90、C99和C++之间还存在一些差异,下面分别进行讨论。

        在C90中,由于左值必须指示一个有效对象(这其实是一个失误),因此C90规定对一个对象的值进行访问只能通过其声明类型(declaration type)进行,这个规定否定了上述代码的指针p访问方式(上述失误导致的问题),但指针p的访问方式是事实上存在且可行的,否定它明显不合理。

        于是,在C99中这个失误得到了修正,C99不再规定左值必须引用一个有效对象,同时引入了有效类型(effective type)这个概念,C99将有效类型分成四种情况,但简单来说,一个具有声明类型的对象的有效类型就是这个声明类型,而一个没有声明类型的对象的有效类型是进行该访问的左值表达式的类型,后一个描述保证了上述指针p访问方式的合法性。C99通过有效类型对对象内容进行操作。

        在C++中,由于引入了OOP,存在继承层次结构的类对象具有多态性,其类型称为动态类型(dynamic type),由运行时刻决定,因此C++对对象的访问是通过动态类型进行的。

抱歉!评论已关闭.