C++ <memory> 库全方位详解

<memory> 是 C++ 标准库中最为核心的头文件之一,它提供了内存管理智能指针的基础设施。从 C++11 开始,它彻底改变了 C++ 开发者管理资源的方式,使得代码更加安全、异常友好且易于维护。

📋整体架构一览

<memory> 库可以按照功能分为以下四大模块:

模块 核心组件 功能描述
智能指针 unique_ptr, shared_ptr, weak_ptr 自动管理动态对象的生命周期,实现 RAII
辅助工具 make_unique, make_shared, allocate_shared 安全、高效地创建智能指针
分配器 allocator, pmr::memory_resource 定义内存分配策略
低级内存操作 addressof, align, uninitialized_* 指针操作、内存对齐、原始内存构造

一、智能指针(Smart Pointers)

智能指针是 <memory> 的核心卖点,它们封装了原始指针,并自动管理所指向对象的生命周期。

1. std::unique_ptr

独占所有权的智能指针,不能被复制,只能被移动

cpp 复制代码
#include <memory>
#include <iostream>

class Widget {
public:
    Widget() { std::cout << "Widget created\n"; }
    ~Widget() { std::cout << "Widget destroyed\n"; }
    void doSomething() { std::cout << "Doing something\n"; }
};

int main() {
    // 创建 unique_ptr(推荐使用 make_unique)
    auto p = std::make_unique<Widget>();
    
    // 调用成员函数
    p->doSomething();
    
    // 不能复制,只能移动
    // auto p2 = p;                    // ❌ 编译错误
    auto p2 = std::move(p);            // ✅ 所有权转移
    
    // 当 p2 超出作用域时,Widget 被自动销毁
    return 0;
}

关键特性

  • 零开销:与原始指针大小相同,没有额外的运行时开销
  • 自定义删除器:可以指定自定义的释放逻辑
  • 数组支持:专门针对数组的特化版本
cpp 复制代码
// 自定义删除器
auto deleter = [](int* p) {
    std::cout << "Custom deleting\n";
    delete p;
};
std::unique_ptr<int, decltype(deleter)> p(new int(42), deleter);

// 数组版本
auto arr = std::make_unique<int[]>(10);  // C++14 起支持
arr[0] = 42;

2. std::shared_ptr

共享所有权的智能指针,通过引用计数管理生命周期。

cpp 复制代码
#include <memory>
#include <iostream>

int main() {
    // 创建 shared_ptr
    std::shared_ptr<int> p1 = std::make_shared<int>(42);
    {
        std::shared_ptr<int> p2 = p1;  // 拷贝,引用计数 +1
        std::cout << "use_count: " << p1.use_count() << std::endl;  // 2
    }  // p2 销毁,引用计数 -1
    
    std::cout << "use_count: " << p1.use_count() << std::endl;  // 1
    return 0;
}  // p1 销毁,引用计数变为 0,内存被释放

关键特性

  • 线程安全:引用计数的增加/减少是线程安全的
  • 控制块:额外分配控制块存储引用计数和删除器
  • 不适合数组:默认不支持数组(C++17 起支持)
cpp 复制代码
// C++17 数组支持
std::shared_ptr<int[]> arr = std::make_shared<int[]>(10);
arr[0] = 42;

// 别名构造函数:共享所有权,但指向不同对象
struct Base { int x; };
struct Derived : Base { int y; };
std::shared_ptr<Derived> d = std::make_shared<Derived>();
std::shared_ptr<Base> b(d, &d->x);  // 共享所有权,但 b 指向 d.x

3. std::weak_ptr

解决 shared_ptr 循环引用的弱引用智能指针。

cpp 复制代码
#include <memory>
#include <iostream>

class Node {
public:
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // 使用 weak_ptr 避免循环引用
    ~Node() { std::cout << "Node destroyed\n"; }
};

int main() {
    auto n1 = std::make_shared<Node>();
    auto n2 = std::make_shared<Node>();
    
    n1->next = n2;
    n2->prev = n1;  // weak_ptr 不增加引用计数
    
    // 安全访问 weak_ptr
    if (auto sp = n2->prev.lock()) {
        std::cout << "Successfully locked\n";
    }
    
    return 0;
}  // 所有 Node 都能被正确销毁

关键用法

  • lock():返回指向对象的 shared_ptr,对象已释放则返回空
  • expired():检查对象是否已被释放

4. 智能指针转换

转换函数 用途 说明
static_pointer_cast 静态转换 类似 static_cast
dynamic_pointer_cast 动态转换 类似 dynamic_cast,失败返回空
const_pointer_cast 常量转换 类似 const_cast
reinterpret_pointer_cast 重解释转换 类似 reinterpret_cast(C++17起)
cpp 复制代码
std::shared_ptr<Base> base = std::make_shared<Derived>();
auto derived = std::dynamic_pointer_cast<Derived>(base);
if (derived) {
    // 转换成功
}

二、创建工具:make_uniquemake_shared

1. std::make_unique (C++14)

cpp 复制代码
// 基本用法
auto p = std::make_unique<Widget>(arg1, arg2);

// 数组版本
auto arr = std::make_unique<int[]>(10);  // C++14

// 自定义删除器的 unique_ptr 不支持 make_unique
std::unique_ptr<Widget, Deleter> p(new Widget(), Deleter());

2. std::make_shared (C++11)

cpp 复制代码
// 一次分配:对象和控制块在同一内存块中
auto p = std::make_shared<Widget>(arg1, arg2);

// 性能优势:比先 new 再 shared_ptr 构造少一次内存分配
// 异常安全:即使有多个参数,构造过程也不会泄露内存

对比 shared_ptr<T>(new T(args))

方面 make_shared shared_ptr<T>(new T)
内存分配次数 1 次 2 次(对象 + 控制块)
异常安全 完全安全 可能存在风险
内存局部性 更好(对象和控制块相邻) 较差
自定义删除器 不支持 支持
weak_ptr 延长生命周期 可能延长 不会

3. std::allocate_shared

使用自定义分配器创建 shared_ptr

cpp 复制代码
#include <memory>

struct MyAllocator : std::allocator<char> {
    // 自定义分配逻辑
};

auto p = std::allocate_shared<Widget>(MyAllocator(), arg1, arg2);

三、分配器(Allocators)

1. 经典分配器 std::allocator

cpp 复制代码
#include <memory>
#include <vector>

int main() {
    std::vector<int, std::allocator<int>> vec;  // 默认分配器
    // allocator 定义了容器的内存分配策略
}

核心接口

  • allocate(n):分配 n 个对象的原始内存
  • deallocate(p, n):释放内存
  • construct(p, args):在已分配内存上构造对象(C++17 移除)
  • destroy(p):析构对象(C++17 移除)

2. 多态内存资源(C++17)

<memory_resource> 子库提供了更现代的内存管理框架:

cpp 复制代码
#include <memory_resource>
#include <vector>
#include <iostream>

int main() {
    // 1. monotonic_buffer_resource:单调增长,极快,不回收
    char buffer[1024];
    std::pmr::monotonic_buffer_resource pool(buffer, sizeof(buffer));
    
    std::pmr::vector<int> vec(&pool);
    for (int i = 0; i < 100; ++i) {
        vec.push_back(i);  // 全部从 pool 中分配
    }
    
    // 2. unsynchronized_pool_resource:对象池,适合固定大小对象
    std::pmr::unsynchronized_pool_resource pool2;
    std::pmr::vector<int> vec2(&pool2);
    
    return 0;
}

可用的 memory_resource

类型 特点 适用场景
new_delete_resource 使用 new/delete 默认行为
null_memory_resource 分配即失败 测试、沙盒
monotonic_buffer_resource 只分配不回收 一次性解析、短期任务
synchronized_pool_resource 线程安全的对象池 多线程环境
unsynchronized_pool_resource 非线程安全对象池 单线程环境

四、低级内存操作工具

1. std::addressof

获取对象的真实地址,即使 operator& 被重载。

cpp 复制代码
struct Evil {
    int* operator&() { return nullptr; }
} evil;

int* p1 = &evil;                   // nullptr(使用了重载的 &)
int* p2 = std::addressof(evil);    // 对象的真实地址

2. std::align

在给定的缓冲区中对齐指针。

cpp 复制代码
#include <memory>
#include <iostream>

int main() {
    char buffer[100];
    void* ptr = buffer;
    size_t space = sizeof(buffer);
    
    // 在 buffer 中找到第一个 16 字节对齐的位置
    if (std::align(16, 10, ptr, space)) {
        std::cout << "Aligned at: " << ptr << std::endl;
        std::cout << "Remaining space: " << space << std::endl;
    }
}

3. 未初始化内存算法

这些函数在已分配但未初始化的内存上操作,是标准库容器实现的基础:

函数 C++11 C++17 功能
uninitialized_copy ✅ (constexpr) 复制构造到未初始化内存
uninitialized_fill ✅ (constexpr) 填充构造到未初始化内存
uninitialized_default_construct 默认构造到未初始化内存
uninitialized_value_construct 值构造到未初始化内存
destroy 销毁对象
cpp 复制代码
#include <memory>
#include <iostream>

int main() {
    // 分配内存但不构造对象
    std::allocator<int> alloc;
    int* p = alloc.allocate(5);
    
    // 在已分配内存上构造对象
    std::uninitialized_fill(p, p + 5, 42);
    
    // 使用对象
    for (int i = 0; i < 5; ++i) {
        std::cout << p[i] << ' ';
    }
    
    // 销毁对象
    std::destroy(p, p + 5);
    
    // 释放内存
    alloc.deallocate(p, 5);
}

4. 原始指针包装

cpp 复制代码
// 从不释放的指针包装
std::observer_ptr<int> obs(p);  // C++26 提案,类似 std::unique_ptr 但不拥有所有权

// 空指针检查
std::not_null<int*> nn(p);      // GSL(Guidelines Support Library)中的工具

五、性能对比与选型建议

智能指针开销对比

智能指针 大小(64位) 分配次数 适用场景
unique_ptr 8 字节 0(对象本身) 独占所有权,90% 的场景
shared_ptr 16 字节(控制块指针2) 1 或 2 次 共享所有权
weak_ptr 16 字节 shared_ptr 相同 打破循环引用

选择指南

场景 推荐方案
默认选择,单个所有者 unique_ptr
需要多个所有者共享对象 shared_ptr
观察但不拥有对象 weak_ptr
避免循环引用 weak_ptr
性能敏感、嵌入式环境 unique_ptr
需要自定义释放逻辑 自定义删除器
缓存或对象池 weak_ptr + shared_ptr
解析 JSON、XML 等一次性任务 monotonic_buffer_resource
避免双重内存分配 make_shared

六、常见陷阱与最佳实践

陷阱 1:使用裸 new 创建 shared_ptr

cpp 复制代码
// ❌ 可能的内存泄漏或双重删除
foo(std::shared_ptr<Widget>(new Widget), bar());

// ✅ 使用 make_shared
foo(std::make_shared<Widget>(), bar());

陷阱 2:循环引用

cpp 复制代码
// ❌ 循环引用导致内存泄漏
struct A {
    std::shared_ptr<A> other;
    ~A() { std::cout << "A destroyed\n"; }
};
auto a1 = std::make_shared<A>();
auto a2 = std::make_shared<A>();
a1->other = a2;
a2->other = a1;  // 循环引用,内存泄漏

// ✅ 使用 weak_ptr 打破循环
struct B {
    std::weak_ptr<B> other;
    ~B() { std::cout << "B destroyed\n"; }
};

陷阱 3:从 this 创建 shared_ptr

cpp 复制代码
class Widget {
public:
    // ❌ 错误:会导致多个控制块
    std::shared_ptr<Widget> get() {
        return std::shared_ptr<Widget>(this);
    }
    
    // ✅ 正确:继承 enable_shared_from_this
    std::shared_ptr<Widget> share() {
        return shared_from_this();
    }
};
class GoodWidget : public std::enable_shared_from_this<GoodWidget> {};

陷阱 4:make_sharedweak_ptr 问题

cpp 复制代码
auto sp = std::make_shared<LargeData>();
std::weak_ptr<LargeData> wp = sp;
sp.reset();  // shared_ptr 释放,但控制块仍然存在
// LargeData 仍然存活!需要等到所有 weak_ptr 也销毁

七、C++11/14/17/20 演进历程

版本 新增特性
C++11 unique_ptr, shared_ptr, weak_ptr, make_shared, addressof, 未初始化算法
C++14 make_unique, shared_ptr 数组支持
C++17 pmr 多态内存资源, uninitialized_* constexpr, reinterpret_pointer_cast, weak_from_this
C++20 make_shared_for_overwrite, allocate_shared_for_overwrite

总结

<memory> 库是现代 C++ 内存管理的基石,它的核心理念是 RAII(资源获取即初始化) 。通过智能指针,C++ 开发者可以编写异常安全、内存安全的代码,无需手动管理 newdelete。同时,分配器和 pmr 为高性能场景提供了灵活的内存管理策略。

开发准则

  • 优先使用 unique_ptr,除非确实需要共享所有权
  • 使用 make_shared / make_unique 而不是裸 new
  • 遇到循环引用时,考虑使用 weak_ptr
  • 不要从裸 this 指针创建 shared_ptr
  • 需要自定义内存策略时,考虑 pmr::memory_resource
相关推荐
代码中介商2 小时前
C++ 类型转换深度解析:static_cast、dynamic_cast、const_cast、reinterpret_cast
开发语言·c++
青小莫2 小时前
C++之string(OJ练习)
开发语言·c++·stl
freshman_y2 小时前
一篇介绍C语言中二级指针和二维数组的文章
c语言·开发语言
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 199. 二叉树的右视图 | C++ DFS 逆序遍历
c++·leetcode·深度优先
-Marks-2 小时前
【C++编程】STL简介 --- (是什么 | 版本发展历程 | 六大组件 | 重要性缺陷以及如何学习)
开发语言·c++·学习·stl·stl版本
HealthScience2 小时前
【Bib 2026】基因最新综述(有什么任务、benchmark、代表性模型)
android·开发语言·kotlin
wjs20242 小时前
CSS 网格元素
开发语言
Java小白笔记3 小时前
OpenClaw 实战方法论
java·开发语言·人工智能·ai·全文检索·ai编程·ai写作