命名对象的生命周期由其作用域决定(见6.3.4节)。然而,某些情况下我们希望对象与创建它的语句所在的作用域独立开来。例如,很多时候我们在函数内部创建了对象,并且希望在函数返回后仍能使用这些对象。运算符new负责创建这样的对象,运算符 delete则负责销毁它们。new分配的对象“位于自由存储之上”(或者说“在堆上”或“在动态内存中”)。
让我们设想一下该为桌面计算器(见10.2节)编写一个怎样的编译器。它的语法分析函数应该会构建一棵表达式树以供代码生成器使用:
struct Encode{Token_value oper;Encode* left;Encode* right;//...};Encode* expr(bool get){Encode* left=term(get);for(;;){switch(ts.current().kind){case Kind::plus:case Kind::minus:left=new Encode{ts.current().kind,left,term(true)};break;default:return left; //返回节点}}}
在 Kind::plus和Kind::minus分支中,我们在自由存储上新建了一个 Enode并将其初始化为{ts. current().kind, left. term(true)}。所得的指针赋给left并最终从expr()返回回来。
我使用{}列表的形式传递实参,当然也可以使用传统的()列表形式指定初始化器。但是,如果试图用符号=初始化一个用new创建的对象,将会引发程序错误:
int* p=new int=7; //错误
如果某一类型含有默认构造函数,则我们可以省略掉初始化器。但是对内置类型这么做的话,其变量将会处于未初始化的状态。例如
auto pc= new complex<double>; //该复数被初始化为{0,0}auto pi= new int; //该int未被初始化
上述设定可能会让人感到有些困惑,更好的办法是使用旮,这样可以确保变量执行默认初始化。例如:
auto pc= new complex<double>;//该复数被初始化为{0,0}auto pi new int{}; //该int被初始化为0
代码生成器先使用expr()创建的 Enode,然后将其删除:
void generate(Enode* n){switch(n->oper){case Kind::plus://使用ndelete n; //从自由存储中删除一个 Enode}}
对于一个用new创建的对象来说,我们必须用 delete显式地将它销毁,否则它将一直存在。只有将它销毁了,它占用的空间才能被其他new使用。有一种思路是建立一个“垃圾回收器”,由它负责看管未引用的对象并使得new能重新使用这些对象所占的空间,但是C++的具体实现并不能确保这一点。因此,我假设new创建的对象需要由 delete手动地释放。delete运算符只能作用于new返回的指针或者 nullptr,不过对 nullptr使用 delete不产生什么实际效果。
如果被删除的对象的类型是一个含有析构函数的类(见3.2.1.2节和17.2节),则 delete将调用该析构函数,然后释放该对象所占的内存空间以供后续使用。
