动态内存指程序在运行时向系统申请、并在适当时机释放的存储区,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 的基础用法。