C++ 的 newdelete 是动态内存核心,但现代代码中应避免裸用。理解其机制避免陷阱:new 分配内存+构造对象,delete 析构+释放内存。

Widget 示例类(全局唯一定义)

#include <print>
#include <format>

class Widget {
public:
    Widget(int value) : value_(value) {
        std::println("Widget constructed with value: {}", value_);
    }
    ~Widget() {
        std::println("Widget destroyed");
    }
private:
    int value_;
};

new 表达式 vs operator new 函数

new 表达式(语言操作符,不可重载):分配内存(调用 operator new)+构造对象,失败抛 std::bad_alloc

operator new(可重载函数):仅分配内存,返回 void*,类似 malloc

// new 表达式(推荐日常)
Widget* p1 = new Widget(42);  // operator new + 构造
delete p1;  // 析构 + operator delete

// operator new(特殊用,如 placement new)
void* raw = operator new(sizeof(Widget));
Widget* p2 = new (raw) Widget(42);  // placement new 构造
p2->~Widget();  // 手动析构
operator delete(raw);  // 手动释放
特性new 表达式operator new
类型操作符函数
功能分配+构造仅分配
返回T*void*
重载

为什么区分:自定义分配(内存池)、placement new、调试拦截。

示例自定义:

void* operator new(std::size_t size) {
    std::println("Allocating {} bytes", size);
    void* p = std::malloc(size);
    if (!p) throw std::bad_alloc{};
    return p;
}
void operator delete(void* p) noexcept { std::free(p); }

变体

数组

int* arr = new int[10]();  // 值初始化为0
delete[] arr;  // 必须配对 new[] + delete[]

铁律:new[] 配 delete[],new 配 delete。混用 UB。

nothrow

#include <new>
int* p = new (std::nothrow) int[1'000'000'000];
if (!p) std::println("Failed");

Placement new

alignas(Widget) unsigned char buf[sizeof(Widget)];
Widget* w = new (buf) Widget(42);
w->~Widget();  // 无 delete

new/delete vs malloc/free

特性new/deletemalloc/free
类型安全
构造/析构
失败bad_allocnullptr
重载

优先 new/delete,除非 C 接口。

失败处理

默认抛异常,可设 handler:

#include <new>
void handler() {
    std::println("OOM!");
    std::set_new_handler(nullptr);
    throw std::bad_alloc{};
}
int main() { std::set_new_handler(handler); /* ... */ }

或用 nothrow。

自定义(类/全局)

类成员重载示例:

class MyClass {
public:
    void* operator new(std::size_t size) {
        return ::operator new(size);  // 调用全局
    }
    void operator delete(void* p) noexcept {
        ::operator delete(p);
    }
};

C++17 对齐:用 std::align_val_t

陷阱 & 最佳实践

  1. 泄漏:忘 delete。:智能指针。
  2. 重复 delete / 野指针:设 nullptr 后 delete。
  3. 悬挂指针:delete 后别用。
  4. 异常安全:构造函数抛异常,内存泄漏。std::make_unique

错误:

Widget* p = new Widget(42);
risky();  // 抛异常,泄漏
delete p;

正确:

auto p = std::make_unique<Widget>(42);
risky();  // 自动释放

现代 C++(优先!)

智能指针<memory>):

auto u = std::make_unique<Widget>(42);  // 独占
auto s = std::make_shared<Widget>(42);  // 共享,性能优

make_ 胜过 new:异常安全,一次分配。

容器

std::vector<Widget> vec;  // 自动管理
vec.emplace_back(42);

RAII:资源生命周期绑对象。

class RAIIResource {
    void* res_;
public:
    RAIIResource() : res_(acquire()) {}
    ~RAIIResource() { release(res_); }
    RAIIResource(const RAIIResource&) = delete;
    RAIIResource(RAIIResource&& o) noexcept : res_(o.res_) { o.res_ = nullptr; }
    // ...
};

性能 & 调试

  • 内存池:高频小对象用,重载 operator new。
  • 对齐alignas(64) 缓存友好。
  • 工具:Valgrind (--leak-check=full),ASan (-fsanitize=address)。

总结

  • 配对:new/delete,new[]/delete[]。
  • 避免裸 new:用 unique_ptr/shared_ptr/vector。
  • RAII:自动管理。
  • 自定义慎用:遵循匹配规则。

现代 C++:裸 new/delete 是例外。智能指针+容器=零泄漏、零手动。

// ✅
auto p = std::make_unique<Widget>(42);

// ❌ 垃圾
Widget* p = new Widget(42); delete p;