Effective C++: 成对使用 new 和 delete 时要采取相同形式
Use the same form in corresponding uses of new and delete
这是 Effective C++ 的第 16 个条款,Use the same form in corresponding uses of new and delete,中文翻译为:成对使用 new 和 delete 时要采取相同形式。
这个条款的核心是 new 和 delete 的形式必须匹配。
- 如果你使用
new来分配单个对象,就必须使用delete来释放。 - 如果你使用
new[]来分配对象数组,就必须使用delete[]来释放。
之所以要怎么做,是因为编译器需要知道要为多少个对象调用析构函数。当使用 new 和 delete 时,底层会发生两件事:
- 内存的分配/释放 (通过
operator new和operator delete函数)。 - 对象的构造/析构 (通过构造函数和析构函数)。
对于单个对象:
std::string* ptr1 = new std::string("hello");
// ...
delete ptr1;cppnew 分配足够一个 std::string 对象的内存,并在这块内存上调用 std::string 的构造函数。delete 对 ptr1 指向的对象调用 std::string 的析构函数,然后释放内存。这里 delete 明确知道只需要调用一次析构函数。
对于对象数组:
std::string* ptr2 = new std::string[3];
// ...
delete[] ptr2;cppnew[]:分配能容纳 3 个 std::string 对象的内存,并依次调用 3 次默认构造函数。delete[] 依次对这 3 个对象调用析构函数(通常是按与构造相反的顺序),然后释放整个内存块。
两者的关键区别在于内存布局。当使用 new[] 分配一个数组时,编译器会在分配的内存块中(通常是在指针返回的地址之前)额外存储数组的大小。当调用 delete[] 时,它会首先查找这个隐藏的 “数组大小” 记录,从而得知需要调用多少次析构函数。当调用 delete 时,它会假定只有一个对象,不会去查找这个大小,因此只会调用一次析构函数。
两者混用会导致未定义行为,即程序可能会崩溃、产生错误结果、或者表面上看起来正常运行但实际上已经造成了资源泄漏或内存损坏。
- 用
delete释放数组:只会调用第一个对象的析构函数,其他对象的析构函数不会被调用,导致资源泄漏。 - 用
delete[]释放单个对象:delete[]会试图在指向的内存块之前读取一个 “数组大小”。但那里并没有存储这个信息,它会读取到一块无意义的数据。这可能会导致它尝试在一个完全错误的位置上执行循环,调用若干次析构函数,最终几乎肯定会损坏内存,导致程序崩溃。
在书中,作者还提到了一个常见的陷阱,即 typedef 可能会隐藏数组的本质,使得我们更容易犯错:
typedef std::string AddressLines[4]; // AddressLines 是一个包含4个string的数组类型
std::string* pal = new AddressLines; // 看起来像是分配单个对象,
// 但实际上等价于 new std::string[4]
delete[] pal; // 正确
// delete pal; // 错误!将导致资源泄漏cpp因此,最佳实践为:不要对数组类型使用 typedef 或 using,以避免混淆。