动态内存指程序在运行时向系统申请、并在适当时机释放的存储区,C++ 中常称自由存储区(free store)。使用 new / delete(及数组形式 new[] / delete[])在堆上分配与释放对象;若只 new 不 delete,会造成内存泄漏。本节在指针基础上说明单对象与数组的配对释放、异常与 nothrow,并引出智能指针与 RAII。
为何需要动态分配
- 对象大小在运行时才确定,或生命周期需跨越创建它的作用域。
- 需要大量同质元素且大小可变时,在手动管理之前应优先考虑
std::vector。 - 多态场景下通过基类指针指向派生对象,见多态;释放时须保证虚析构正确。
单对象分配
#include <iostream>
int main() {
int* p = new int(42);
std::cout << *p << "\n";
delete p;
p = nullptr;
return 0;
}new int(42) 分配足够容纳一个 int 的存储并初始化;delete p 释放该存储。释放后对 p 解引用属于未定义行为,宜将指针置为 nullptr。
new 的几种形式
| 表达式 | 说明 |
|---|---|
new T | 默认初始化(对类类型调用默认构造) |
new T() | 值初始化(标量常为 0) |
new T(args...) | 直接初始化,调用匹配构造函数 |
数组分配
#include <iostream>
int main() {
int* arr = new int[5]{1, 2, 3, 4, 5};
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
std::cout << "\n";
delete[] arr;
return 0;
}典型输出:1 2 3 4 5。数组必须用 delete[];与单对象 delete 混用属于未定义行为。优先使用 std::vector 或 std::unique_ptr<T[]>,见vector与智能指针。
类类型的 new / delete
#include <iostream>
#include <string>
struct Task {
std::string name;
Task(const std::string& n) : name(n) {
std::cout << "构造 " << name << "\n";
}
~Task() { std::cout << "析构 " << name << "\n"; }
};
int main() {
Task* t = new Task("write");
delete t;
return 0;
}典型输出:先打印构造,再打印析构。delete 会先调用析构函数,再释放存储。
分配失败与 nothrow
默认情况下 new 分配失败时抛出 std::bad_alloc,可用 try / catch 处理,见异常。若希望失败时返回空指针而不抛异常,可使用:
int* p = new (std::nothrow) int(100);
if (p == nullptr) {
// 分配失败
}头文件 <new> 提供 std::nothrow。
常见错误
- 泄漏:丢失指针且未
delete,或异常路径上遗漏释放。 - 重复释放:对同一地址
delete两次未定义。 - 数组与单对象混用:
new[]必须配delete[]。 - 悬垂指针:
delete后仍解引用;释放后应置nullptr或让智能指针接管。
与 RAII 的关系
资源获取即初始化(RAII):在对象构造时取得资源,在析构时释放。裸 new / delete 分散在业务逻辑中易遗漏;标准库容器与智能指针将释放绑定到对象生命周期,异常展开时也能正确析构,见异常与栈展开。
本节说明了 new、delete 及数组形式的配对规则与常见陷阱。下一节介绍智能指针:unique_ptr 与 shared_ptr 的基础用法。