Contents

[读书笔记] 深入探索C++对象模型 第六章 执行期语意学

执行期语意学

以下一个简单的式子

1
if (yy == xx.getValue)

xx yy定义如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
X xx;
Y yy;
class Y
{
public:
  Y();
 ~Y();
  bool operator==(const& Y) const;
};
class X
{
public:
  X();
  ~X();
  operator Y() const;
  X getValue();
};

那么编译器在我们之后做了什么呢

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
if(yy == xx.getValue())

//转换为
if(yy.operator==(xx.getValue())

//接着转换
if(yy.operator==(xx.getValue().operator Y()))

//接着转换
X temp1 = xx.getValue();
Y temp2 = temp1.operator Y();
int temp3 = (yy.operator==(temp2));
if(temp3)

temp2.y::~Y();
temp1.x::~X();

对象的构造与析构

局部对象

1
2
3
4
5
6
{
 Point p;
 // p.Point::Point();
 ...
 //p.Point::~Point();
 }

如果一个函数拥有多个离开点,那么会在每一个离开点之前对对象进行析构。

全局对象

1
2
3
4
5
6
7
Matrix identity;
main()
{
    Matrix ml = identity;
    ...
    return 0;
}

C++ 保证一定会在main()中第一次用到identity之前把 didentity构造出来,在main()函数结束之前销毁。 C++程序中所有全局对象都被防止在程序的data segment中,如果明确指定给它一个值,object将以该值为初值。否咋object所配置到的内存内容为0。

  • class object在编译器可以被放置与data sement中并且为0,但是它的构造函数需要在程序激活的时候才会被实施。(也就是说全局对象的初始化的问题,对于类对象的有些门道。)
 (还是不是很清楚全局对象是如何初始化的) ### 局部静态对象 ```cpp const Matrix& identity() { static Matrix mat_identity; ... return mat_identity; } ``` 对于局部静态的变量,他们的构造和析构必须只施行一次。 编译器的策略是,导入一个临时性的对象以保护mat_identity的初始化操作。第一次处理identity()时候,这临时对象被评估为false,于是构造函数被调用,然后临时对象改为true。同理析构也是如此。 (但是具体现代编译器怎么操作的我还是不清楚。)。 ### 对象数组 ```cpp Point knots[10]; ``` 需要做什么。如果是一个没有构造函数的,也没有析构函数的。那么工作不会比建立一个内建类型所组成的数组更多。 如果有的话,那么整齐的操作必须施行与每一个元素上。 在cfront中,使用一个命名为vec_new()的函数,产生以class objects构造而成的数组。 ```cpp void* ver_new( void *array, //数组的起始位置 size_t elem_size, //一个对象的大小 int elem_count, //数组的元素个数 void (*constructor)( void*), void (*destructor)(void*, char) ) ``` 调用操作
1
2
Point knots[10];
ver_new(&knots, sizeof(Point), 10, &Point::Point, 0);

同样如果Point有一个析构函数会有一个类似ver_delete()的函数

1
2
3
4
5
6
7
void* 
ver_delete(
    void *array,                            //数组的起始位置
    size_t elem_size,                       //一个对象的大小
    int elem_count,                         //数组的元素个数
    void (*destructor)(void*, char)
)

不同的编译器会有不同的实现。 如果数组部分被赋予了初值的,那么会产生什么转换

1
2
3
4
5
Point knots[10] = {
    Point(),
    Point(1.0, 1.0, 0.5),
    -1.0
};

对于有了初值的元素ver_new不必要,但是未被初始化的部分会调用vec_new。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Point knots[10] = {
    Point(),
    Point(1.0, 1.0, 0.5),
    -1.0
};

//明确的初始化前三个
Point::Point(&knots[0]);
Point::Point(&knots[1], 1.0, 1.0, 0.5);
Point::Point(&knots[2], -1.0, 0.0, 0.0);
ver_new(&knots + 3, sizeof(Point), 7, &Point::Point, 0);

default Constructors和数组

为了支持

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
complex:: complex(double = 0.0, double = 0.0);
complext c_array[10];
//编译器最终调用
vec_new(&c_array, sizeof(complex), 10, &complex::complex, 0);
//cfront采用如下方法支持
//产生一个默认构造函数 调用带默认参数的构造函数
complex::complex()
{
    complex(0.0,0.0);
}
//来完成调用

(有一个问题,那么这个不就是产生了两个不带参数的构造函数吗,虽然一个有参数,但是都用默认的。怎么解决的。不过大部分构造过程都是在编译期间,那么都是静态指定调用的话,还是解决掉了的。不是很清楚这个问题。)

new delete运算符

运算符new的使用,之前的几章一直都有。 会转换成两步,一步是使用适当的函数,分配内存。 后一步是给对象设置初值,类对象的话,调用的对应的构造函数等等

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
extern void* operator new( size_t size )   
{   
    if( size == 0 )   
    size = 1; // 这里保证像 new T[0] 这样得语句也是可行的   
   
    void *last_alloc;   
    while( !(last_alloc = malloc( size )) )   
    {   
       if( _new_handler )   
           ( *_new_handler )(); //调用handler函数  
        else   
           return 0;   
    }   
    return last_alloc;         
}   
extern void operator delete( void *ptr )   
{   
    if(ptr) // 从这里可以看出,删除一个空指针是安全的   
    free( (char*)ptr );   
}  

针对数组的new语意

内建的或者没有默认构造函数的,直接默认的new就能完成任务。 对于有默认构造函数的,某些版本的vec_new()就会被调用。

临时性对象

很多简短的代码实际上都会产生一些临时对象。 是不是真的产生,需要看编译器的具体实现了。

临时性对象的迷思