第6章 执行期语意学
6.0 引言
对如下的类定义以及操作:
class Y {
public:
Y();
~Y();
bool operator==( const Y& ) const;
};
class X {
public:
X();
~X();
operator Y() const; // conversion
X getValue();
};
那么如下的表达式:
if ( yy == xx.getValue() )
则在执行时被转化为:
{
X temp1 = xx.getValue(); //引入X型的临时变量
Y temp2 = temp1.operator Y(); //引入Y类型的临时变量
int temp3 = yy.operator == (temp2); //引入int型的临时变量
if ( temp3 )
……
temp2.Y::~Y(); //Y析构函数
temp1.X::~X(); //X析构函数
}
6.1 对象的构造与析构
注意:如果一个区段或函数中有多个离开点,那么析构函数必须放在每一个离开点之前(如果当时对象还有效的话)。
6.1.1 全局对象
对如下程序片断:
Matrix identity;
int main() {
Matrix m1 = identity;
return 0;
}
C++保证,一定会在main函数中第一次用到identity前,把identity构造出来,而在main函数结束之前把identity销毁掉。如果这样的全局对象有构造函数和析构函数的话,将需要静态的初始化与内存释放操作。
C++程序中所有的全局对象都被放置在程序的数据段中,如果明确指定给它一个值,则该对象以该值作为初值,否则对象所配置的内存内容为0(C中并不自动设置初值)。
6.1.2 局部静态对象
函数内的局部静态对象只能构造一次以及析构一次,而且在执行到该定义点处在构造。在cfront中确保这个动作正确地完成引入一个临时量来表示是否已构造,而在析构时则通过取出其数据段中的地址来有条件地调用确保正确。
6.1.3 对象数组
对于数组定义:
Point knot[ 10 ];
如果Point没有定义构造函数也没有定义析构函数,那么其工作不会比建立一个内建类型所组成的数组更多,及只需要配置足够的内存以存储10个连续的Point元素。但若是定义了构造函数和析构函数,则必须轮流地施行于每一个元素之上。在cfront中,使用一个vec_new()函数,产生出以类对象构造而成的数组,vec_new()的声明一般如下:
void* vec_new(void* array, size_t elem_size, int elem_count,
void (*constructor )( void* ), void (*destructor) ( void*, char ));
同样,在销毁时采用对应的vec_delete函数,其声明如下:
void* vec_delete( void* array, size_t elem_size, int elem_count,
void (*destructor)( void*, char ));
6.1.4 默认构造函数与数组
没多大意义,因此忽略其内部所做的实现。
6.2 new与delete运算符
略。详细讨论见“C++动态内存创建与内存管理学习笔记”。
6.3 临时对象
在C++中真正的临时对象是看不见的,它们不出现在你的源代码中。建立一个没有命名的非堆(non-heap)对象会产生临时对象。这种未命名的对象通常在两种条件下产生:为了使函数成功调用而进行隐式类型转换和函数返回对象时。
在任何时候只要见到常量引用(reference-to-const)参数,就存在建立临时对象而绑定在参数上的可能性。在任何时候只要见到函数返回对象,就会有一个临时对象被建立(以后被释放)。
对于临时对象的生存期问题,有如下的原则:临时对象的摧毁,应该是在造成临时对象产生的完整表达式求值过程中的最后一个步骤。但有两个例外:
1. 表达式被用来初始化一个对象时,要求存留到对象的初始化操作完成为止;
2. 当一个临时对象被一个引用绑定时,对象将在引用的声明结束或临时对象的声明范畴结束之(视哪一种情况先到达而定)时才销毁。