C++对象模型学习笔记6 执行期 语意学

开篇

  • {P239}

	X xx;
	Y yy;
{ 
	if (yy == xx.getValue())}
其实被扩展成:
{
  	X temp1 = xx.getValue();
  	Y temp2 = temp1.operator Y();
  	int temp3 = yy.operator==(temp2);
  	If (temp3) 
	… 
 
  	temp2.Y::~Y();
 	 temp1.X::~X();
}
  • 不容易从源代码看出表达式的复杂度。

对象的构造和析构

  • {P241} 一般而言,我们将object尽可能放置在使用它的那个程序区段附近,这样可以节省非必要的对象的构造和析构成本
全局对象
  • C++程序中所有的global objects都被放置在程序的data segment中,如果不明确指定初值,object所配置的内存内容将为0(C并不自动设定初值)。如果global object有ctor和dtor的话,我们说它需要静态的初始化和内存释放操作。

  • {P243} munch策略(全局变量静态初始化):

    1. 为每一个需要静态初始化的文件产生一个__sti()函数(sti: static initialization),内含必要的constructor调用操作或者inline expansions。
    2. 为每一个需要静态的内存释放操作的文件中,产生__std()函数(std: static deallocation),内含必要的constructor调用操作或者inline expansions。
    3. 提供一组running library”munch”函数:一个_main()函数(用于调用所有的__sti()函数),以及一个exit()函数(用于调用所有的__std()函数)。
  • {P245} 在特定平台上的C++编译器开始出现,更有效率的方法也就有可能随之出现。eg: system V的ELF被扩充以增加支持.init.fini两个sections,这两个sections内含对象所需的信息,分别对应于静态初始化和释放操作。编译器特定的startup函数(通常名为crt0.o)会完成这个平台特定的支持(分别针对静态初始化和释放操作的支持)。

局部静态对象
  • {P249} 第一次进函数时才需要初始化,所以编译其间无法知道它们的顺序和集合。一般在这个函数中判断一个静态临时性对象。eg:
static struct Matrix *__0__F3 = 0;
Foo…
{
	static struct Matrix __1mat_indentity;
        __0__F3 ? 0 : (__ct__6MatrixFv(&__1mat_indentity), __0__F3 = (&__1mat_indentity));
}

最后一个统一的dtor函数来判断和回收它们。

Char __std__stat_0_c_j(){
        …
	__0__F3? __dt__6MatrixFv(__0__F3, 2) : 0;}
  • 新的规则要求编译单位中的局部静态对象必须被摧毁,而且以构造相反的顺序摧毁。

对象数组

{P250} 在cfront中,我们使用一个名为vec_new的函数,以产生以class objects构造而成的数组。一些新近编译器,支持含有虚函数的情况,使用vec_vnew()。

void vec_new(
    void* array,   	 //数组起始地址
    size_t elem_size,    // 每一个class object大小
    int elem_count,     // 数组中的元素个数
    void (*constructor)(void*),  // 零个形参的默认构造函数指针
    void (*destructor)(void*, char) //析构函数指针
)
Point knots[10];
vec_new(&knots, sizeof(Point), 10, Point::Point, 0);
  • 同理,析构数组的函数有vec_delete(如果含有虚函数,使用vec_vdelete)。
void vec_delete(
    void* array,    //数组起始地址
    size_t elem_size,    //每一个class object大小
    int elem_count,     //数组中的元素个数
    void (*destructor)(void*, char) //析构函数指针
)
Default ctors和数组
  • {P252} 如果一个类的构造函数有一个或一个以上的默认参数值,使用stub ctor来过度。
class complex {
    complex(double = 0.0, double = 0.0);
}

那么当我们写下complex c_array[10];时,编译器最终需要调用:

vec_new(&c_array, sizeof(complex), 10, &complex::complex, 0);

这里的complex::complex是stub ctor,在里面调用用户写的ctor。

complex::complex() {
    complex(0.0, 0.0);
}

new 和 delete 运算符

内置类型 {P254}
	int *pi = new int(5);
=> 
	int *pi;
	if(pi = __new(sizeof(int))) // 1. 配置所需内存
 	   *pi = 5;		    // 2. 赋初值
	delete pi; 
=>
	if(pi != 0) __delete(pi);
非内置类型 {P255}
  • new的处理
Point3d *origin = new Point3d;

=>
Point3d *origin;
if(origin = __new(sizeof(Point3d)))
    origin->Point3d::Point3d(origin);
// 支持异常处理
Point3d *origin;
if(origin = __new(sizeof(Point3d))) {
    try {
        origin->Point3d::Point3d(origin);
    }
    catch(...) {
        __delete(origin) // 回收内存
        throw;  // 继续往上抛
    }
}
  • delete的处理
delete origin;

=>

if(origin != 0) {
    Point3d::~Point3d(origin);
    __delete(origin);
}
new的实现(请注意,以下版本并未考虑EH)extern void* operator new(size_t size) {
    if(size == 0)   // 第一个精妙之处,这里对于size为零特殊处理,
    		    // 返回一个独一无二的指针。
	size = 1;
    
    void *last_alloc;
    while(!(last_alloc = malloc(size))) {
        if(_new_handler) 
          (*_new_handler)();   // 第二个精妙之处,
        		    // 允许用户提供自己的_new_handler(),
        		    // 每次循环都调用。
        else 
	  return 0;
    }
    return last_alloc;
}
  • new运算符实际上总是以标准的C malloc()完成,虽然并没有规定一定得这么做不可。相同情况,delete函数也总是以标准的free()完成:
extern void operator delete(void* ptr) {
    if(ptr)
        free((char*) ptr);
}
针对数组的new语意 {P257 }

当我们这么写int* p_array = new int[5];时,vec_new不会真正被调用,因为它的主要功能是将default ctor施加于class object所组成的每个数组元素上。int没有默认构造函数,倒是new运算符函数会被调用:

int* p_array = (int *) __new(5 * sizeof(int));

同理,以下vec_new也不会被调用,因为以下结构没有ctor或dtor。

struct simple_aggr { float f1, f2; }
simple_aggr* p_aggr = new simple_aggr[5];
Placemenet Operator new的语意
  • {P263} 有一个预先定义好的重载的new运算符,称为placement operator new。它需要第二个参数,类型为void*:
void* operator new (size_t, void* p) 
{ 
  return p; 
}

它的效果相当于:

Point2w *ptw = (Point2w*) arena;
if (ptw != 0)
  ptw->Point2w::Point2w(); 
  • 有一个轻微的不良行为。
void fooBar() {
  Point2w *p2w = new (arena) Point2w;
  // ... do it ...
  // ... now manipulate a new object
  p2w = new (arena) Point2w;
}
  • 如果前面在这个arena的类有析构函数,那么需要调用该析构。调用该析构的方法之一是将该指针delete掉。但问题来了,这样回将这块空间回收,那么接下来无法继续使用该空间继续去new。正确的方法是显式调用dtor,如下:
	p2w->~Point2w();
	p2w = new (arena) Point2w();

临时性对象

  • {P267} 有时候会产生临时性对象,视编译器的进取性(aggressiveness)以及上述操作发生时的程序语境而定。C++标准允许编译器在这方面有完全的自由度。
  • 对于以下的代码,可能有一个临时对象来保存a+b的结果,也可能直接用a+b的结果来拷贝构造c。
    T operator+(const T&, const T&);
    T a, b;
    T c = a + b;
  • {P268} 但以下的代码只有细微的差距,但却无法不产生临时对象:
   T c;
   c = a + b;
等价于:
   T c;
   T temp;
   temp.operator+(a, b);
   c.operator=(temp);  // 需要调用赋值重载
   temp.T::~T();

所以一般来说 T c = a + b;更会有效率


  • {P271} 临时对象的被摧毁,应该是对完整表达式(full-expression)求值过程中的最后一个步骤。完整表达式,非正式的说,它是被涵括的表达式中最外围的那个。但它有两个例外:
  1. 凡是有表达式执行结果的临时性对象,应该保存到object的初始化操作完成为止。string proNameVersion = !verbose ? 0 : proName + progVersion; 临时对象来存储求和,它需要在整个表达式结束评估后尽快被摧毁。
  2. 当一个临时对象被一个reference绑定时。如果一个临时对象被绑定于一个reference,对象将残留,直到被初始化之ref的声明结束,或直到临时对象的生命范畴结束。const String &space = " ";
例外1:
  String temp;
  operator+ (temp, progName, progVersion);
  progNameVersion = temp.String::operator char*();
  temp.String::~String();
例外2:
  String temp;
  temp.String::String(" ");
  const String &space = temp;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值