命名对象的生命周期由其作用域决定(见6.3.4节)。然而,某些情况下我们希望对象与创建它的语句所在的作用域独立开来。例如,很多时候我们在函数内部创建了对象,并且希望在函数返回后仍能使用这些对象。运算符new负责创建这样的对象,运算符 delete则负责销毁它们。new分配的对象“位于自由存储之上”(或者说“在堆上”或“在动态内存中”)。
    让我们设想一下该为桌面计算器(见10.2节)编写一个怎样的编译器。它的语法分析函数应该会构建一棵表达式树以供代码生成器使用:

    1. struct Encode
    2. {
    3. Token_value oper;
    4. Encode* left;
    5. Encode* right;
    6. //...
    7. };
    8. Encode* expr(bool get)
    9. {
    10. Encode* left=term(get);
    11. for(;;)
    12. {
    13. switch(ts.current().kind)
    14. {
    15. case Kind::plus:
    16. case Kind::minus:
    17. left=new Encode{ts.current().kind,left,term(true)};
    18. break;
    19. default:
    20. return left; //返回节点
    21. }
    22. }
    23. }

    Kind::plusKind::minus分支中,我们在自由存储上新建了一个 Enode并将其初始化为{ts. current().kind, left. term(true)}。所得的指针赋给left并最终从expr()返回回来。
    我使用{}列表的形式传递实参,当然也可以使用传统的()列表形式指定初始化器。但是,如果试图用符号=初始化一个用new创建的对象,将会引发程序错误:

    1. int* p=new int=7; //错误

    如果某一类型含有默认构造函数,则我们可以省略掉初始化器。但是对内置类型这么做的话,其变量将会处于未初始化的状态。例如

    1. auto pc= new complex<double>; //该复数被初始化为{0,0}
    2. auto pi= new int; //该int未被初始化

    上述设定可能会让人感到有些困惑,更好的办法是使用旮,这样可以确保变量执行默认初始化。例如:

    1. auto pc= new complex<double>;//该复数被初始化为{0,0}
    2. auto pi new int{}; //该int被初始化为0

    代码生成器先使用expr()创建的 Enode,然后将其删除:

    1. void generate(Enode* n)
    2. {
    3. switch(n->oper)
    4. {
    5. case Kind::plus:
    6. //使用n
    7. delete n; //从自由存储中删除一个 Enode
    8. }
    9. }

    对于一个用new创建的对象来说,我们必须用 delete显式地将它销毁,否则它将一直存在。只有将它销毁了,它占用的空间才能被其他new使用。有一种思路是建立一个“垃圾回收器”,由它负责看管未引用的对象并使得new能重新使用这些对象所占的空间,但是C++的具体实现并不能确保这一点。因此,我假设new创建的对象需要由 delete手动地释放。
    delete运算符只能作用于new返回的指针或者 nullptr,不过对 nullptr使用 delete不产生什么实际效果。
    如果被删除的对象的类型是一个含有析构函数的类(见3.2.1.2节和17.2节),则 delete将调用该析构函数,然后释放该对象所占的内存空间以供后续使用。