第11章 内存与资源管理

内存管理基础

内存区域

C++程序运行时,内存通常分为以下几个区域,每个区域都有其特定的用途、管理方式和性能特点:

  1. 代码区(Text Segment)

    • 存储程序的可执行机器代码,通常是只读的,以防止程序意外修改自身指令
    • 包含函数体的二进制指令,按函数地址顺序排列
    • 支持内存保护,防止执行非代码区域的数据
    • 在多进程环境中,相同程序的代码区可以共享,减少内存使用
  2. 只读数据区(RODATA)

    • 存储字符串字面量、const修饰的全局变量和其他常量
    • 同样是只读的,防止运行时修改
    • 字符串字面量会被去重,相同的字符串只存储一份
    • 例如:const char* str = "Hello" 中的 “Hello” 存储在此区域
  3. 全局/静态区(Data Segment)

    • 存储初始化的全局变量和静态变量
    • 程序启动时分配,结束时释放
    • 变量在编译时就确定了地址
    • 例如:int globalVar = 42static int staticVar = 100 存储在此区域
  4. 未初始化数据区(BSS Segment)

    • 存储未初始化的全局变量和静态变量
    • 程序启动时会被自动初始化为0
    • 不占用可执行文件空间,仅在运行时分配内存
    • 例如:int uninitGlobalVar;static int uninitStaticVar; 存储在此区域
  5. 堆区(Heap)

    • 动态分配的内存,由程序员通过new/deletemalloc/free管理
    • 分配和释放的顺序可以任意,形成自由内存块
    • 空间较大,是程序动态内存的主要来源
    • 内存分配器负责管理空闲内存块,处理分配和释放请求
    • 可能产生内存碎片,影响内存利用率
  6. 栈区(Stack)

    • 存储函数调用时的局部变量、函数参数、返回地址和调用帧信息
    • 由编译器自动管理,遵循后进先出(LIFO)原则
    • 分配和释放速度极快,仅需修改栈指针
    • 空间有限,通常为几MB,超出会导致栈溢出
    • 函数调用结束时,自动释放该函数的栈帧

内存布局示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
高地址
┌─────────────────────────────────┐
│ 栈区 (Stack) │ ← 向下增长
├─────────────────────────────────┤
│ 堆区 (Heap) │ ← 向上增长
├─────────────────────────────────┤
│ 未初始化数据区 (BSS Segment) │
├─────────────────────────────────┤
│ 初始化数据区 (Data Segment) │
├─────────────────────────────────┤
│ 只读数据区 (RODATA) │
├─────────────────────────────────┤
│ 代码区 (Text Segment) │
└─────────────────────────────────┘
低地址

内存分配方式

C++中有三种基本内存分配方式,每种方式都有其特定的适用场景和性能特点:

  1. 静态分配

    • 分配时机:编译时由编译器分配
    • 存储位置:数据段或BSS段
    • 生命周期:程序启动到程序结束
    • 管理方式:编译器自动管理,无需手动释放
    • 优点
      • 分配速度快,无运行时开销
      • 地址固定,访问速度快
      • 无需手动管理,避免内存泄漏
    • 缺点
      • 生命周期固定,无法在运行时调整
      • 空间有限,受限于数据段大小
      • 全局可见,可能导致命名冲突
    • 适用场景
      • 存储程序运行期间始终需要的数据
      • 配置参数和全局状态
      • 常量和只读数据
  2. 栈分配

    • 分配时机:函数调用时由编译器自动分配
    • 存储位置:栈区
    • 生命周期:函数调用开始到函数返回
    • 管理方式:编译器自动管理,函数返回时自动释放
    • 优点
      • 分配和释放速度极快,仅需修改栈指针
      • 无需手动管理,避免内存泄漏
      • 作用域明确,提高代码可读性
      • 支持RAII(资源获取即初始化)
    • 缺点
      • 空间有限,通常为几MB
      • 生命周期受限,函数返回后自动释放
      • 不适合存储大型数据结构
    • 适用场景
      • 存储函数局部变量和参数
      • 临时数据和中间计算结果
      • 生命周期与函数调用一致的资源
  3. 堆分配

    • 分配时机:运行时通过内存分配器手动分配
    • 存储位置:堆区
    • 生命周期:从分配到手动释放
    • 管理方式:程序员手动管理,需要配对使用分配和释放函数
    • 优点
      • 空间较大,适合存储大型数据结构
      • 生命周期灵活,可由程序员控制
      • 支持动态调整大小
    • 缺点
      • 分配和释放速度较慢
      • 需要手动管理,容易导致内存泄漏
      • 可能产生内存碎片
      • 地址不固定,访问速度相对较慢
    • 适用场景
      • 存储大小不确定的数据
      • 生命周期与函数调用无关的数据
      • 大型数据结构和容器

性能比较

分配方式分配速度空间大小灵活性安全性访问速度
栈分配极快极快
静态分配极快极快
堆分配

内存分配的底层实现

  1. 栈分配

    • 通过修改栈指针(ESP/RSP寄存器)实现
    • 分配时栈指针减少(向低地址方向移动)
    • 释放时栈指针增加(向高地址方向移动)
    • 栈帧结构包含:返回地址、前栈指针、函数参数、局部变量
    • 栈帧大小在编译时确定,运行时无计算开销
  2. 堆分配

    • 通过内存分配器(如ptmalloc、tcmalloc、jemalloc)管理
    • 维护空闲内存块链表或位图
    • 分配时查找合适大小的块(首次适应、最佳适应、最坏适应算法)
    • 释放时将块放回空闲链表,可能合并相邻空闲块
    • 处理内存碎片,提高内存利用率
  3. 静态分配

    • 在程序加载时由操作系统加载器分配
    • 位于数据段或BSS段
    • 变量地址在编译时确定,存储在可执行文件的符号表中
    • 初始化的全局变量存储在数据段,未初始化的存储在BSS段

内存分配的高级特性

  1. 内存对齐

    • 变量或对象在内存中的起始地址必须是某个值的倍数
    • 提高内存访问速度,减少CPU读取次数
    • 某些硬件和指令集要求数据必须对齐
    • 使用alignas关键字和std::aligned_alloc函数控制
  2. 内存屏障

    • 确保内存操作的顺序性,防止编译器和CPU重排序
    • 用于多线程编程中的同步
    • 使用std::atomic和内存序控制
  3. 内存映射

    • 将文件或设备映射到进程的虚拟内存空间
    • 提供高效的文件I/O操作
    • 使用mmap系统调用(POSIX)或等效API
  4. 虚拟内存

    • 提供逻辑地址到物理地址的映射
    • 支持内存保护和分页管理
    • 允许程序使用比物理内存更大的地址空间

内存管理的挑战

  1. 内存泄漏

    • 动态分配的内存未释放,导致内存使用量不断增加
    • 常见原因:忘记释放、异常导致的提前返回、循环引用
    • 解决方案:使用智能指针、RAII、内存分析工具
  2. 内存碎片

    • 频繁分配和释放小块内存导致的内存空间不连续
    • 分为内部碎片(分配块大于请求大小)和外部碎片(空闲块分散)
    • 解决方案:内存池、对象池、区域分配器
  3. 悬空指针

    • 指向已释放内存的指针
    • 解引用悬空指针会导致未定义行为
    • 解决方案:释放后将指针置为nullptr、使用智能指针
  4. 野指针

    • 未初始化或指向无效内存的指针
    • 解引用野指针会导致未定义行为
    • 解决方案:始终初始化指针、使用智能指针
  5. 内存溢出

    • 栈溢出:递归过深或局部变量过大
    • 堆溢出:分配的内存超过可用堆空间
    • 解决方案:合理设计递归、使用动态内存、监控内存使用

内存管理的最佳实践

  1. 优先使用栈分配

    • 对于小对象和临时数据,优先使用栈分配
    • 利用栈分配的速度优势和自动管理特性
  2. 合理使用静态分配

    • 对于全局常量和配置数据,使用静态分配
    • 避免过度使用全局变量,减少命名冲突
  3. 谨慎使用堆分配

    • 对于大型数据结构和动态大小的数据,使用堆分配
    • 始终使用RAII和智能指针管理堆内存
  4. 监控内存使用

    • 使用内存分析工具(如Valgrind、AddressSanitizer)检测内存问题
    • 定期检查内存使用情况,避免内存泄漏
  5. 优化内存分配

    • 减少动态内存分配次数,使用批量分配
    • 对于频繁分配的小对象,使用内存池
    • 合理设计数据结构,减少内存占用

动态内存分配

newdelete运算符

C++使用newdelete运算符进行动态内存分配和释放,它们在C的malloc/free基础上增加了对象的构造和析构功能,提供了更安全、更符合C++语言特性的内存管理方式。

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
// 分配单个对象
int* p = new int;
*p = 42;
std::cout << *p << std::endl;
delete p;

// 分配数组
int* arr = new int[10];
for (int i = 0; i < 10; ++i) {
arr[i] = i;
}
delete[] arr;

初始化方式

C++支持多种初始化方式,适应不同的场景需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 1. 值初始化:使用括号初始化单个对象
int* p1 = new int(100);

// 2. 列表初始化:使用花括号初始化单个对象(C++11+)
Point* p2 = new Point{10, 20};

// 3. 默认初始化:不进行初始化,值为未定义(内置类型)
int* p3 = new int;

// 4. 零初始化:使用空花括号,内置类型初始化为0
int* p4 = new int{};

// 5. 数组初始化:使用花括号初始化数组(C++11+)
int* arr1 = new int[5]{1, 2, 3, 4, 5};

// 6. 数组零初始化:使用空花括号,所有元素初始化为0
int* arr2 = new int[5]{};

// 7. 数组默认初始化:不进行初始化,值为未定义(内置类型)
int* arr3 = new int[5];

// 8. 定位初始化:在指定内存位置构造对象
char buffer[sizeof(Point)];
Point* p5 = new (buffer) Point{30, 40};

定位new(Placement New)

定位new允许在指定的内存位置创建对象,而不分配新的内存,是实现内存池、对象池和自定义分配器的基础。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <new>

// 预分配内存
alignas(alignof(int)) char buffer[sizeof(int)];

// 在指定位置构造对象
int* p = new (buffer) int(42);

// 使用对象
std::cout << *p << std::endl;

// 不需要使用delete,因为内存不是从堆分配的
// 但如果对象有析构函数,需要手动调用
// p->~int(); // 对于内置类型,析构函数调用是可选的

// 重用内存位置构造新对象
new (buffer) int(100);
std::cout << *p << std::endl; // 输出100

定位new的高级应用

  1. 内存池:预分配大块内存,然后在其中构造对象,减少内存分配开销
  2. 自定义分配器:实现特定的内存分配策略,如线程本地存储、对齐分配等
  3. 对象重用:在同一内存位置重复构造不同类型的对象,提高内存利用率
  4. 内存布局控制:精确控制对象在内存中的位置,用于硬件交互或性能优化
  5. 异常安全:在复杂的初始化过程中,确保内存分配和对象构造的原子性

newdelete的底层实现

new运算符的工作过程:

  1. 调用operator new分配原始内存
  2. 在分配的内存上调用构造函数
  3. 返回指向新对象的指针

delete运算符的工作过程:

  1. 调用对象的析构函数
  2. 调用operator delete释放内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// new的伪代码实现
template <typename T, typename... Args>
T* new_impl(Args&&... args) {
// 1. 分配原始内存
void* memory = operator new(sizeof(T));

try {
// 2. 在分配的内存上调用构造函数
new (memory) T(std::forward<Args>(args)...);
return static_cast<T*>(memory);
} catch (...) {
// 3. 构造失败时释放内存
operator delete(memory);
throw;
}
}

// delete的伪代码实现
template <typename T>
void delete_impl(T* ptr) {
if (ptr) {
// 1. 调用对象的析构函数
ptr->~T();
// 2. 释放内存
operator delete(ptr);
}
}

// new[]的伪代码实现
template <typename T>
T* new_array_impl(size_t size) {
// 1. 分配原始内存(包含数组大小信息)
void* memory = operator new[](size * sizeof(T) + sizeof(size_t));

// 2. 存储数组大小
*static_cast<size_t*>(memory) = size;

try {
// 3. 在分配的内存上构造对象
T* objects = static_cast<T*>(static_cast<char*>(memory) + sizeof(size_t));
for (size_t i = 0; i < size; ++i) {
new (objects + i) T();
}
return objects;
} catch (...) {
// 4. 构造失败时释放内存
operator delete[](memory);
throw;
}
}

// delete[]的伪代码实现
template <typename T>
void delete_array_impl(T* ptr) {
if (ptr) {
// 1. 获取数组大小
size_t size = *static_cast<size_t*>(static_cast<char*>(ptr) - sizeof(size_t));

// 2. 调用对象的析构函数
for (size_t i = size; i > 0; --i) {
(ptr + i - 1)->~T();
}

// 3. 释放内存
operator delete[](static_cast<char*>(ptr) - sizeof(size_t));
}
}

重载newdelete运算符

C++允许重载newdelete运算符,以实现自定义的内存分配策略,满足特定场景的需求。

  1. 全局重载:影响整个程序的内存分配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 全局重载operator new
void* operator new(size_t size) {
std::cout << "Custom new called for size " << size << std::endl;
void* p = std::malloc(size);
if (!p) {
throw std::bad_alloc{};
}
return p;
}

// 全局重载operator delete
void operator delete(void* p) noexcept {
std::cout << "Custom delete called" << std::endl;
std::free(p);
}

// 重载operator new[]
void* operator new[](size_t size) {
std::cout << "Custom new[] called for size " << size << std::endl;
return operator new(size);
}

// 重载operator delete[]
void operator delete[](void* p) noexcept {
std::cout << "Custom delete[] called" << std::endl;
operator delete(p);
}

// 重载nothrow版本
void* operator new(size_t size, const std::nothrow_t&) noexcept {
try {
return operator new(size);
} catch (...) {
return nullptr;
}
}
  1. 类级重载:仅影响特定类的对象分配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class MyClass {
public:
// 类级重载operator new
void* operator new(size_t size) {
std::cout << "MyClass new called for size " << size << std::endl;
void* p = std::malloc(size);
if (!p) {
throw std::bad_alloc{};
}
return p;
}

// 类级重载operator delete
void operator delete(void* p) noexcept {
std::cout << "MyClass delete called" << std::endl;
std::free(p);
}

// 数组版本
void* operator new[](size_t size) {
std::cout << "MyClass new[] called for size " << size << std::endl;
void* p = std::malloc(size);
if (!p) {
throw std::bad_alloc{};
}
return p;
}

void operator delete[](void* p) noexcept {
std::cout << "MyClass delete[] called" << std::endl;
std::free(p);
}

// nothrow版本
void* operator new(size_t size, const std::nothrow_t&) noexcept {
try {
return operator new(size);
} catch (...) {
return nullptr;
}
}

void operator delete(void* p, const std::nothrow_t&) noexcept {
operator delete(p);
}
};
  1. 放置版本重载:自定义定位new的行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 自定义放置new
void* operator new(size_t size, const char* file, int line) {
std::cout << "Placement new called from " << file << ":" << line << std::endl;
return operator new(size);
}

// 对应的delete(仅在构造函数抛出异常时调用)
void operator delete(void* p, const char* file, int line) noexcept {
std::cout << "Placement delete called from " << file << ":" << line << std::endl;
operator delete(p);
}

// 另一个放置版本
void* operator new(size_t size, alignas(16) char* buffer) {
std::cout << "Aligned placement new called" << std::endl;
return buffer;
}

void operator delete(void* p, alignas(16) char* buffer) noexcept {
std::cout << "Aligned placement delete called" << std::endl;
// 不需要释放内存,因为使用的是用户提供的缓冲区
}

// 使用示例
#define DEBUG_NEW new(__FILE__, __LINE__)
MyClass* p = DEBUG_NEW MyClass();
delete p;

// 使用对齐放置new
alignas(16) char buffer[sizeof(MyClass)];
MyClass* p2 = new (buffer) MyClass();
p2->~MyClass(); // 手动调用析构函数

内存分配策略

  1. 内存池

    • 预分配大块内存,然后将其分割成小块进行分配
    • 减少内存碎片,提高分配速度
    • 适用于频繁分配和释放小块内存的场景
  2. 对象池

    • 为特定类型的对象预分配内存
    • 避免频繁的构造和析构
    • 适用于对象创建成本高的场景
  3. 区域分配器

    • 为特定生命周期的对象分配内存
    • 一次性释放整个区域
    • 适用于批量创建和销毁对象的场景
  4. 缓存分配器

    • 缓存最近释放的内存块
    • 以加速后续的分配请求
    • 适用于分配模式可预测的场景
  5. 对齐分配器

    • 确保内存分配满足特定的对齐要求
    • 提高内存访问速度
    • 适用于需要特定对齐的硬件或指令

内存池实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
class MemoryPool {
public:
MemoryPool(size_t blockSize, size_t blockCount)
: blockSize(blockSize), blockCount(blockCount), pool(nullptr), freeList(nullptr) {
// 确保blockSize至少能容纳一个指针
if (blockSize < sizeof(void*)) {
blockSize = sizeof(void*);
}

// 分配大块内存
pool = static_cast<char*>(std::malloc(blockSize * blockCount));
if (!pool) {
throw std::bad_alloc{};
}

// 初始化自由块链表
for (size_t i = 0; i < blockCount; ++i) {
char* block = pool + i * blockSize;
*reinterpret_cast<char**>(block) = freeList;
freeList = block;
}
}

~MemoryPool() {
if (pool) {
std::free(pool);
}
}

// 分配内存
void* allocate() {
if (!freeList) {
throw std::bad_alloc{};
}

void* block = freeList;
freeList = *reinterpret_cast<char**>(freeList);
allocatedBlocks++;
return block;
}

// 释放内存
void deallocate(void* ptr) {
if (!ptr) return;

// 验证指针是否在内存池中
if (ptr < pool || ptr >= pool + blockSize * blockCount) {
throw std::invalid_argument("Pointer not from this memory pool");
}

// 确保指针对齐到块大小
size_t offset = static_cast<char*>(ptr) - pool;
if (offset % blockSize != 0) {
throw std::invalid_argument("Pointer not properly aligned");
}

// 将块放回自由链表
*reinterpret_cast<char**>(ptr) = freeList;
freeList = static_cast<char*>(ptr);
allocatedBlocks--;
}

// 获取内存池状态
size_t getBlockSize() const { return blockSize; }
size_t getBlockCount() const { return blockCount; }
size_t getAllocatedBlocks() const { return allocatedBlocks; }
size_t getFreeBlocks() const { return blockCount - allocatedBlocks; }
bool isFull() const { return freeList == nullptr; }
bool isEmpty() const { return allocatedBlocks == 0; }

// 禁止拷贝和移动
MemoryPool(const MemoryPool&) = delete;
MemoryPool& operator=(const MemoryPool&) = delete;
MemoryPool(MemoryPool&&) = delete;
MemoryPool& operator=(MemoryPool&&) = delete;

private:
size_t blockSize;
size_t blockCount;
char* pool;
char* freeList;
size_t allocatedBlocks = 0;
};

// 使用内存池
MemoryPool pool(sizeof(int), 100);
int* p = static_cast<int*>(pool.allocate());
*p = 42;
std::cout << *p << std::endl;
pool.deallocate(p);

// 内存池状态查询
std::cout << "Block size: " << pool.getBlockSize() << std::endl;
std::cout << "Total blocks: " << pool.getBlockCount() << std::endl;
std::cout << "Allocated blocks: " << pool.getAllocatedBlocks() << std::endl;
std::cout << "Free blocks: " << pool.getFreeBlocks() << std::endl;

对象池实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
template <typename T>
class ObjectPool {
public:
ObjectPool(size_t capacity) : capacity(capacity), size(0) {
// 预分配内存
pool = static_cast<T*>(std::malloc(sizeof(T) * capacity));
if (!pool) {
throw std::bad_alloc{};
}

// 初始化自由对象链表
for (size_t i = 0; i < capacity; ++i) {
T* obj = pool + i;
freeObjects.push_back(obj);
}
}

~ObjectPool() {
// 销毁所有已分配的对象
for (T* obj : allocatedObjects) {
obj->~T();
}

// 释放内存
std::free(pool);
}

// 分配对象
template <typename... Args>
T* allocate(Args&&... args) {
if (freeObjects.empty()) {
throw std::bad_alloc{};
}

// 从自由链表中获取对象
T* obj = freeObjects.back();
freeObjects.pop_back();

// 在内存位置构造对象
new (obj) T(std::forward<Args>(args)...);

// 添加到已分配列表
allocatedObjects.push_back(obj);
size++;

return obj;
}

// 释放对象
void deallocate(T* obj) {
if (!obj) return;

// 验证对象是否在对象池中
if (obj < pool || obj >= pool + capacity) {
throw std::invalid_argument("Object not from this pool");
}

// 查找并从已分配列表中移除
auto it = std::find(allocatedObjects.begin(), allocatedObjects.end(), obj);
if (it == allocatedObjects.end()) {
throw std::invalid_argument("Object not allocated from this pool");
}
allocatedObjects.erase(it);

// 调用析构函数
obj->~T();

// 将对象放回自由链表
freeObjects.push_back(obj);
size--;
}

// 获取对象池状态
size_t getCapacity() const { return capacity; }
size_t getSize() const { return size; }
size_t getFreeCount() const { return freeObjects.size(); }
bool isFull() const { return freeObjects.empty(); }
bool isEmpty() const { return size == 0; }

// 禁止拷贝和移动
ObjectPool(const ObjectPool&) = delete;
ObjectPool& operator=(const ObjectPool&) = delete;
ObjectPool(ObjectPool&&) = delete;
ObjectPool& operator=(ObjectPool&&) = delete;

private:
size_t capacity;
size_t size;
T* pool;
std::vector<T*> freeObjects;
std::vector<T*> allocatedObjects;
};

// 使用对象池
class MyObject {
public:
MyObject(int v) : value(v) {
std::cout << "MyObject constructed with value " << value << std::endl;
}

~MyObject() {
std::cout << "MyObject destructed with value " << value << std::endl;
}

int getValue() const { return value; }

private:
int value;
};

ObjectPool<MyObject> objPool(5);

// 分配对象
MyObject* obj1 = objPool.allocate(10);
MyObject* obj2 = objPool.allocate(20);

// 使用对象
std::cout << "obj1 value: " << obj1->getValue() << std::endl;
std::cout << "obj2 value: " << obj2->getValue() << std::endl;

// 释放对象
objPool.deallocate(obj1);
objPool.deallocate(obj2);

// 重用对象
MyObject* obj3 = objPool.allocate(30);
std::cout << "obj3 value: " << obj3->getValue() << std::endl;
objPool.deallocate(obj3);

智能指针

C++11引入了智能指针,用于自动管理动态内存,避免内存泄漏。智能指针通过RAII(资源获取即初始化)机制,在对象生命周期结束时自动释放所管理的资源。

std::unique_ptr

std::unique_ptr是一种独占所有权的智能指针,同一时间只能有一个unique_ptr指向同一个对象,所有权可以转移但不能复制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <memory>

// 创建unique_ptr
std::unique_ptr<int> p1(new int(42));

// 使用make_unique(推荐,更安全,避免异常安全问题)
auto p2 = std::make_unique<int>(100);

// 转移所有权
std::unique_ptr<int> p3 = std::move(p1); // p1现在为空

// 访问对象
std::cout << *p2 << std::endl;
std::cout << *p3 << std::endl;

// 检查是否为空
if (p1) {
std::cout << *p1 << std::endl;
} else {
std::cout << "p1 is null" << std::endl;
}

// 重置智能指针
p2.reset(); // 释放p2管理的内存

// 替换所管理的对象
p3.reset(new int(200)); // 释放旧对象,管理新对象

// 获取原始指针
int* rawPtr = p3.get();

// unique_ptr会自动释放内存,不需要手动调用delete

std::unique_ptr的性能优势

  • 大小与原始指针相同(通常为8字节)
  • 无引用计数开销
  • 移动操作开销极小
  • 适用于大多数需要独占所有权的场景

std::unique_ptr与数组

1
2
3
4
5
6
7
8
9
// 管理数组
std::unique_ptr<int[]> arr(new int[10]);
for (int i = 0; i < 10; ++i) {
arr[i] = i;
}
// 自动使用delete[]释放

// 使用make_unique创建数组(C++14+)
auto arr2 = std::make_unique<int[]>(10);

std::shared_ptr

std::shared_ptr是一种共享所有权的智能指针,多个shared_ptr可以指向同一个对象,使用引用计数来管理内存。当最后一个shared_ptr被销毁时,对象才会被释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <memory>

// 创建shared_ptr
std::shared_ptr<int> p1(new int(42));

// 使用make_shared(推荐,更高效,只分配一次内存)
auto p2 = std::make_shared<int>(100);

// 共享所有权
std::shared_ptr<int> p3 = p1;

// 访问对象
std::cout << *p1 << std::endl;
std::cout << *p2 << std::endl;
std::cout << *p3 << std::endl;

// 查看引用计数
std::cout << "p1 use_count: " << p1.use_count() << std::endl; // 输出2

// 重置shared_ptr
p1.reset(); // 引用计数减1,现在为1
std::cout << "p3 use_count after p1.reset(): " << p3.use_count() << std::endl; // 输出1

// shared_ptr会在最后一个引用被销毁时自动释放内存

std::shared_ptr的内部实现

  • 控制块:存储引用计数、弱引用计数和自定义删除器
  • 内存布局:当使用make_shared时,控制块和对象在同一块内存中分配,减少内存开销

std::shared_ptr的性能考虑

  • 大小为两个指针(通常为16字节)
  • 引用计数操作是原子的,有一定的线程安全开销
  • 避免使用shared_ptr(new T()),优先使用make_shared<T>()

std::weak_ptr

std::weak_ptr是一种不增加引用计数的智能指针,用于解决shared_ptr的循环引用问题,也可以用于观察对象是否存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <memory>

class B;

class A {
public:
std::shared_ptr<B> b;
~A() { std::cout << "A destroyed" << std::endl; }
};

class B {
public:
std::weak_ptr<A> a; // 使用weak_ptr避免循环引用
~B() { std::cout << "B destroyed" << std::endl; }
};

int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();

a->b = b;
b->a = a;

// 检查引用计数
std::cout << "a use_count: " << a.use_count() << std::endl; // 输出1
std::cout << "b use_count: " << b.use_count() << std::endl; // 输出2

// 从weak_ptr获取shared_ptr
if (auto locked = b->a.lock()) {
std::cout << "A still exists" << std::endl;
} else {
std::cout << "A has been destroyed" << std::endl;
}

// 不会导致内存泄漏,两个对象都会被正确销毁
return 0;
}

std::weak_ptr的应用场景

  • 解决循环引用问题
  • 观察者模式:观察者持有被观察对象的弱引用
  • 缓存:缓存对象的弱引用,避免缓存导致对象无法释放

自定义删除器

智能指针支持自定义删除器,用于处理特殊的资源释放逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <memory>
#include <fstream>

// 自定义删除器(函数对象)
struct FileDeleter {
void operator()(std::FILE* fp) {
if (fp) {
std::fclose(fp);
std::cout << "File closed" << std::endl;
}
}
};

// 使用自定义删除器
std::unique_ptr<std::FILE, FileDeleter> filePtr(std::fopen("test.txt", "w"));

// 使用lambda作为删除器
auto deleter = [](int* p) {
std::cout << "Custom deleter called" << std::endl;
delete p;
};

std::unique_ptr<int, decltype(deleter)> p(new int(42), deleter);

// shared_ptr使用自定义删除器
std::shared_ptr<int> sp(new int(100), [](int* p) {
std::cout << "Shared deleter called" << std::endl;
delete p;
});

// 管理数组(使用lambda删除器)
std::shared_ptr<int> arrSp(new int[10], [](int* p) {
delete[] p;
});

自定义删除器的应用场景

  • 管理非堆内存资源(如文件句柄、网络连接、数据库连接)
  • 实现特殊的资源释放逻辑(如解锁互斥锁、通知其他线程)
  • 与第三方库集成,使用库特定的释放函数

智能指针的实现原理

std::unique_ptr的深度实现

std::unique_ptr的核心设计理念是零开销抽象,它的大小与裸指针相同,只在需要时才会增加删除器的大小。下面是一个更接近标准库实现的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
template <typename T, typename Deleter = std::default_delete<T>>
class UniquePtr {
public:
// 构造函数
constexpr UniquePtr() noexcept : ptr_(nullptr) {}
explicit UniquePtr(T* ptr) noexcept : ptr_(ptr) {}

// 移动构造函数
UniquePtr(UniquePtr&& other) noexcept
: ptr_(other.ptr_), deleter_(std::move(other.deleter_)) {
other.ptr_ = nullptr;
}

// 模板移动构造函数(支持派生类到基类的转换)
template <typename U, typename D,
typename = std::enable_if_t<std::is_convertible_v<U*, T*>>>
UniquePtr(UniquePtr<U, D>&& other) noexcept
: ptr_(other.release()), deleter_(std::move(other.get_deleter())) {}

// 析构函数
~UniquePtr() {
if (ptr_) {
deleter_(ptr_);
}
}

// 移动赋值运算符
UniquePtr& operator=(UniquePtr&& other) noexcept {
if (this != &other) {
reset(other.release());
deleter_ = std::move(other.deleter_);
}
return *this;
}

// 模板移动赋值运算符
template <typename U, typename D>
std::enable_if_t<std::is_convertible_v<U*, T*>, UniquePtr&>
operator=(UniquePtr<U, D>&& other) noexcept {
reset(other.release());
deleter_ = std::move(other.get_deleter());
return *this;
}

// 禁止拷贝
UniquePtr(const UniquePtr&) = delete;
UniquePtr& operator=(const UniquePtr&) = delete;

// 指针操作
T& operator*() const noexcept { return *ptr_; }
T* operator->() const noexcept { return ptr_; }
T* get() const noexcept { return ptr_; }

// 获取删除器
Deleter& get_deleter() noexcept { return deleter_; }
const Deleter& get_deleter() const noexcept { return deleter_; }

// 释放所有权
T* release() noexcept {
T* temp = ptr_;
ptr_ = nullptr;
return temp;
}

// 重置
void reset(T* ptr = nullptr) noexcept {
T* old_ptr = ptr_;
ptr_ = ptr;
if (old_ptr) {
deleter_(old_ptr);
}
}

// 交换
void swap(UniquePtr& other) noexcept {
std::swap(ptr_, other.ptr_);
std::swap(deleter_, other.deleter_);
}

// 布尔转换
explicit operator bool() const noexcept { return ptr_ != nullptr; }

private:
T* ptr_;
Deleter deleter_;
};

// 数组特化
template <typename T, typename Deleter>
class UniquePtr<T[], Deleter> {
public:
// 类似实现,但使用delete[]
constexpr UniquePtr() noexcept : ptr_(nullptr) {}
explicit UniquePtr(T* ptr) noexcept : ptr_(ptr) {}

// 其他成员函数类似...

// 数组访问运算符
T& operator[](std::size_t index) const noexcept {
return ptr_[index];
}

private:
T* ptr_;
Deleter deleter_;
};

// 辅助函数
template <typename T, typename... Args>
std::enable_if_t<!std::is_array_v<T>, UniquePtr<T>>
make_unique(Args&&... args) {
return UniquePtr<T>(new T(std::forward<Args>(args)...));
}

template <typename T>
std::enable_if_t<std::is_array_v<T>, UniquePtr<T>>
make_unique(std::size_t size) {
using ElemType = std::remove_extent_t<T>;
return UniquePtr<T>(new ElemType[size]());
}

std::shared_ptr的深度实现

std::shared_ptr的核心是引用计数和控制块,下面是一个更接近标准库实现的版本,包含了弱引用计数和线程安全的引用计数操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
template <typename T>
class SharedPtr {
public:
// 构造函数
constexpr SharedPtr() noexcept : control_block_(nullptr) {}
explicit SharedPtr(T* ptr) {
if (ptr) {
control_block_ = new ControlBlock(ptr);
}
}

// 拷贝构造函数
SharedPtr(const SharedPtr& other) noexcept {
if (other.control_block_) {
control_block_ = other.control_block_;
control_block_->add_ref();
}
}

// 移动构造函数
SharedPtr(SharedPtr&& other) noexcept : control_block_(other.control_block_) {
other.control_block_ = nullptr;
}

// 从weak_ptr构造
SharedPtr(const WeakPtr<T>& other) {
if (!other.expired()) {
control_block_ = other.control_block_;
control_block_->add_ref();
}
}

// 析构函数
~SharedPtr() {
if (control_block_) {
if (control_block_->release_ref() == 0) {
delete control_block_;
}
}
}

// 拷贝赋值运算符
SharedPtr& operator=(const SharedPtr& other) noexcept {
if (this != &other) {
SharedPtr temp(other);
swap(temp);
}
return *this;
}

// 移动赋值运算符
SharedPtr& operator=(SharedPtr&& other) noexcept {
if (this != &other) {
SharedPtr temp(std::move(other));
swap(temp);
}
return *this;
}

// 指针操作
T& operator*() const noexcept { return *control_block_->ptr_; }
T* operator->() const noexcept { return control_block_->ptr_; }
T* get() const noexcept { return control_block_ ? control_block_->ptr_ : nullptr; }

// 引用计数
long use_count() const noexcept {
return control_block_ ? control_block_->use_count() : 0;
}

// 检查是否是唯一所有者
bool unique() const noexcept {
return use_count() == 1;
}

// 交换
void swap(SharedPtr& other) noexcept {
std::swap(control_block_, other.control_block_);
}

// 重置
void reset() noexcept {
SharedPtr().swap(*this);
}

template <typename U>
void reset(U* ptr) {
SharedPtr(ptr).swap(*this);
}

private:
// 控制块
struct ControlBlock {
T* ptr_;
std::atomic<long> use_count_;
std::atomic<long> weak_count_;

ControlBlock(T* ptr) : ptr_(ptr), use_count_(1), weak_count_(1) {}

~ControlBlock() {
delete ptr_;
}

void add_ref() {
++use_count_;
}

long release_ref() {
long count = --use_count_;
if (count == 0) {
// 释放资源
delete ptr_;
ptr_ = nullptr;
// 检查弱引用计数
if (--weak_count_ == 0) {
delete this;
}
}
return count;
}

void add_weak_ref() {
++weak_count_;
}

long release_weak_ref() {
long count = --weak_count_;
if (count == 0 && use_count_ == 0) {
delete this;
}
return count;
}

long use_count() const {
return use_count_.load();
}

long weak_count() const {
return weak_count_.load();
}
};

ControlBlock* control_block_;

// 用于weak_ptr的访问
friend class WeakPtr<T>;
};

// weak_ptr的实现
template <typename T>
class WeakPtr {
public:
// 构造函数
constexpr WeakPtr() noexcept : control_block_(nullptr) {}

// 从shared_ptr构造
WeakPtr(const SharedPtr<T>& other) noexcept {
if (other.control_block_) {
control_block_ = other.control_block_;
control_block_->add_weak_ref();
}
}

// 拷贝构造函数
WeakPtr(const WeakPtr& other) noexcept {
if (other.control_block_) {
control_block_ = other.control_block_;
control_block_->add_weak_ref();
}
}

// 移动构造函数
WeakPtr(WeakPtr&& other) noexcept : control_block_(other.control_block_) {
other.control_block_ = nullptr;
}

// 析构函数
~WeakPtr() {
if (control_block_) {
control_block_->release_weak_ref();
}
}

// 赋值运算符
WeakPtr& operator=(const WeakPtr& other) noexcept {
if (this != &other) {
WeakPtr temp(other);
swap(temp);
}
return *this;
}

WeakPtr& operator=(WeakPtr&& other) noexcept {
if (this != &other) {
WeakPtr temp(std::move(other));
swap(temp);
}
return *this;
}

WeakPtr& operator=(const SharedPtr<T>& other) noexcept {
WeakPtr temp(other);
swap(temp);
return *this;
}

// 检查是否过期
bool expired() const noexcept {
return use_count() == 0;
}

// 获取shared_ptr
SharedPtr<T> lock() const noexcept {
if (expired()) {
return SharedPtr<T>();
}
return SharedPtr<T>(*this);
}

// 引用计数
long use_count() const noexcept {
return control_block_ ? control_block_->use_count() : 0;
}

// 交换
void swap(WeakPtr& other) noexcept {
std::swap(control_block_, other.control_block_);
}

private:
typename SharedPtr<T>::ControlBlock* control_block_;

// 用于shared_ptr的访问
friend class SharedPtr<T>;
};

// 辅助函数
template <typename T, typename... Args>
SharedPtr<T> make_shared(Args&&... args) {
// 优化:将控制块和对象在同一块内存中分配
struct ControlBlockWithObject : public SharedPtr<T>::ControlBlock {
std::aligned_storage_t<sizeof(T), alignof(T)> storage;

ControlBlockWithObject(Args&&... args)
: ControlBlock(nullptr) {
ptr_ = new (&storage) T(std::forward<Args>(args)...);
}

~ControlBlockWithObject() {
// 析构函数由基类调用
}
};

auto cb = new ControlBlockWithObject(std::forward<Args>(args)...);
SharedPtr<T> ptr;
ptr.control_block_ = cb;
return ptr;
}

智能指针的高级特性

智能指针的类型转换

C++标准库提供了三种智能指针的类型转换函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <memory>

// static_pointer_cast:静态类型转换,用于继承层次结构中的上行和下行转换
auto basePtr = std::make_shared<Base>();
auto derivedPtr = std::static_pointer_cast<Derived>(basePtr);

// dynamic_pointer_cast:动态类型转换,用于安全的下行转换,失败返回空指针
auto basePtr2 = std::make_shared<Base>();
auto derivedPtr2 = std::dynamic_pointer_cast<Derived>(basePtr2);
if (derivedPtr2) {
// 转换成功
} else {
// 转换失败
}

// const_pointer_cast:常量类型转换,用于添加或移除const限定符
std::shared_ptr<const int> constPtr = std::make_shared<int>(42);
std::shared_ptr<int> nonConstPtr = std::const_pointer_cast<int>(constPtr);
*nonConstPtr = 100; // 现在可以修改值
智能指针与自定义分配器

C++11及以上版本支持为shared_ptr指定自定义分配器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <memory>
#include <vector>

// 自定义分配器
class MyAllocator {
public:
using value_type = char;

MyAllocator() = default;

template <typename U>
MyAllocator(const MyAllocator&) noexcept {}

char* allocate(std::size_t n) {
std::cout << "Allocating " << n << " bytes" << std::endl;
return static_cast<char*>(std::malloc(n));
}

void deallocate(char* p, std::size_t n) noexcept {
std::cout << "Deallocating " << n << " bytes" << std::endl;
std::free(p);
}
};

// 使用自定义分配器创建shared_ptr
void useCustomAllocator() {
auto deleter = [](int* p) {
std::cout << "Deleting int: " << *p << std::endl;
delete p;
};

std::allocator_arg_t arg;
MyAllocator alloc;

// 使用allocator_arg_t标记和自定义分配器
std::shared_ptr<int> p(
std::allocator_arg,
alloc,
new int(42),
deleter
);

std::cout << *p << std::endl;
}
智能指针的性能优化技巧
  1. 优先使用std::make_uniquestd::make_shared

    • 它们更安全,避免了异常安全问题
    • std::make_shared更高效,因为它只分配一次内存
  2. 避免不必要的智能指针拷贝

    • 对于shared_ptr,拷贝操作会增加引用计数,有一定开销
    • 尽量使用引用或指针传递智能指针
  3. 合理使用移动语义

    • 对于unique_ptr,使用std::move转移所有权
    • 对于shared_ptr,移动操作比拷贝操作更高效
  4. 使用std::weak_ptr作为观察者

    • 当只需要观察对象而不延长其生命周期时,使用weak_ptr
    • 避免使用shared_ptr导致的不必要的引用计数
  5. 避免循环引用

    • 使用weak_ptr打破循环引用
    • 考虑使用所有权明确的设计,如父子关系中父对象拥有子对象
  6. 合理选择智能指针类型

    • 当资源只需要一个所有者时,使用unique_ptr
    • 当资源需要多个所有者时,使用shared_ptr
    • 当只需要观察资源时,使用weak_ptr
智能指针的最佳实践和陷阱

最佳实践

  1. 始终使用智能指针管理动态内存

    • 避免手动调用newdelete
    • 减少内存泄漏的风险
  2. 使用std::make_uniquestd::make_shared创建智能指针

    • 更简洁,更安全,更高效
  3. 优先使用std::unique_ptr

    • 零开销,所有权明确
    • 当需要共享所有权时,再考虑std::shared_ptr
  4. 使用std::weak_ptr解决循环引用

    • 在双向引用关系中,使用weak_ptr打破循环
  5. 避免裸指针与智能指针混用

    • 不要让裸指针的生命周期超过智能指针的生命周期
    • 尽量使用智能指针的get()方法获取裸指针,且只在必要时使用
  6. 使用自定义删除器管理非内存资源

    • 如文件句柄、网络连接、数据库连接等

常见陷阱

  1. 循环引用

    • 两个或多个shared_ptr相互引用,导致引用计数永远不为0
    • 解决方案:使用weak_ptr打破循环
  2. 裸指针赋值给多个智能指针

    • 同一个裸指针被多个智能指针管理,导致重复释放
    • 解决方案:始终使用std::make_uniquestd::make_shared,或确保只有一个智能指针管理资源
  3. 智能指针的析构函数抛出异常

    • 如果智能指针的删除器抛出异常,可能导致程序终止
    • 解决方案:确保删除器不抛出异常
  4. 过度使用shared_ptr

    • 所有对象都使用shared_ptr管理,导致不必要的引用计数开销
    • 解决方案:优先使用unique_ptr,只在需要共享所有权时使用shared_ptr
  5. shared_ptr的线程安全性误解

    • 认为shared_ptr管理的对象也是线程安全的
    • 解决方案:shared_ptr只保证引用计数的线程安全,对象的线程安全需要额外的同步措施
  6. 智能指针与数组的误用

    • 使用std::shared_ptr<int>管理数组,导致使用delete而不是delete[]
    • 解决方案:在C++17及以上版本使用std::shared_ptr<int[]>,在C++14及以下版本使用自定义删除器

智能指针与多线程

std::shared_ptr的线程安全性

  • 引用计数的操作是线程安全的(原子操作)
  • 多个线程可以同时读取和修改同一个shared_ptr的引用计数
  • 但对shared_ptr所管理对象的访问不是线程安全的,需要额外的同步措施

std::unique_ptr的线程安全性

  • 不提供线程安全保证
  • 同一时间只能有一个线程访问同一个unique_ptr
  • 可以通过移动语义在不同线程间传递所有权

多线程环境下的智能指针使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <memory>
#include <thread>
#include <mutex>
#include <vector>

// 示例1:共享数据的线程安全访问
class SharedData {
public:
SharedData(int value) : value_(value) {}

void increment() {
std::lock_guard<std::mutex> lock(mutex_);
++value_;
}

int get() const {
std::lock_guard<std::mutex> lock(mutex_);
return value_;
}

private:
mutable std::mutex mutex_;
int value_;
};

void threadFunction(std::shared_ptr<SharedData> data, int iterations) {
for (int i = 0; i < iterations; ++i) {
data->increment();
}
}

// 示例2:线程间传递unique_ptr
void processData(std::unique_ptr<int> data) {
std::cout << "Processing data: " << *data << std::endl;
}

int main() {
// 示例1:共享数据
auto sharedData = std::make_shared<SharedData>(0);
std::vector<std::thread> threads;

for (int i = 0; i < 4; ++i) {
threads.emplace_back(threadFunction, sharedData, 100000);
}

for (auto& thread : threads) {
thread.join();
}

std::cout << "Final value: " << sharedData->get() << std::endl;

// 示例2:传递unique_ptr
std::thread t(processData, std::make_unique<int>(42));
t.join();

return 0;
}

线程安全的智能指针包装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <memory>
#include <mutex>

// 线程安全的shared_ptr包装器
template <typename T>
class ThreadSafeSharedPtr {
public:
ThreadSafeSharedPtr() = default;

template <typename... Args>
explicit ThreadSafeSharedPtr(Args&&... args)
: ptr_(std::make_shared<T>(std::forward<Args>(args)...)) {}

// 线程安全的访问
template <typename Func>
auto withLock(Func&& func) {
std::lock_guard<std::mutex> lock(mutex_);
return func(*ptr_);
}

// 线程安全的修改
template <typename Func>
auto withLockMut(Func&& func) {
std::lock_guard<std::mutex> lock(mutex_);
return func(*ptr_);
}

private:
std::shared_ptr<T> ptr_;
mutable std::mutex mutex_;
};

// 使用示例
void useThreadSafeSharedPtr() {
ThreadSafeSharedPtr<int> value(42);

// 线程安全的读取
int currentValue = value.withLock([](const int& v) {
return v;
});

// 线程安全的修改
value.withLockMut([](int& v) {
v = 100;
});
}

RAII(资源获取即初始化)

RAII的核心概念与设计哲学

RAII(Resource Acquisition Is Initialization)是C++中最基本、最重要的资源管理技术,其核心思想是将资源的生命周期与对象的生命周期绑定,通过对象的构造和析构来自动管理资源。

RAII的基本原理

  1. 资源获取时初始化:在构造函数中获取资源(如内存、文件句柄、网络连接等)
  2. 资源在对象生命周期内保持有效:只要对象存在,资源就有效
  3. 资源在对象销毁时释放:在析构函数中自动释放资源

RAII的理论基础

  • 栈展开(Stack Unwinding):当异常发生时,C++会自动展开栈,调用所有局部对象的析构函数
  • 析构函数的保证执行:无论函数是正常返回还是因异常退出,局部对象的析构函数都会被调用
  • 作用域约束:RAII对象的作用域决定了资源的生命周期,提供了清晰的资源管理边界

RAII的优势

  • 自动资源管理:无需手动释放资源,避免资源泄漏
  • 异常安全性:即使发生异常,析构函数也会被调用,确保资源释放
  • 代码简洁:将资源管理逻辑集中在构造和析构函数中,提高代码可读性
  • 作用域控制:通过对象的作用域控制资源的生命周期,避免悬空资源
  • 可组合性:RAII对象可以组合使用,形成更复杂的资源管理结构

RAII的高级应用场景

RAII是C++中最基本的资源管理技术,几乎所有C++代码都应该使用RAII来管理资源。以下是RAII的常见高级应用场景:

  1. 内存管理:使用智能指针(std::unique_ptrstd::shared_ptr)自动管理动态内存
  2. 文件操作:使用RAII包装器管理文件句柄,确保文件正确关闭
  3. 网络连接:使用RAII包装器管理网络套接字,确保连接正确关闭
  4. 同步原语:使用std::lock_guardstd::unique_lock等管理互斥锁,确保锁的正确释放
  5. 数据库连接:使用RAII包装器管理数据库连接,确保连接正确关闭
  6. 线程管理:使用std::thread的RAII语义管理线程,确保线程资源正确释放
  7. 事务管理:使用RAII管理数据库事务,确保事务正确提交或回滚
  8. 其他系统资源:如注册表句柄、GDI对象、COM接口等

自定义RAII类的高级实现

通用RAII包装器的深度实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// 通用RAII包装器,支持任意资源和释放操作
template <typename Resource, typename Deleter = std::default_delete<Resource>>
class RAIIWrapper {
public:
// 构造函数:获取资源
RAIIWrapper(Resource resource, Deleter deleter = Deleter{})
: resource_(resource), deleter_(std::move(deleter)), owns_(true) {}

// 移动构造函数
RAIIWrapper(RAIIWrapper&& other) noexcept
: resource_(std::move(other.resource_)),
deleter_(std::move(other.deleter_)),
owns_(other.owns_) {
other.owns_ = false;
}

// 析构函数:释放资源
~RAIIWrapper() {
if (owns_) {
deleter_(resource_);
}
}

// 禁止拷贝
RAIIWrapper(const RAIIWrapper&) = delete;
RAIIWrapper& operator=(const RAIIWrapper&) = delete;

// 移动赋值运算符
RAIIWrapper& operator=(RAIIWrapper&& other) noexcept {
if (this != &other) {
reset();
resource_ = std::move(other.resource_);
deleter_ = std::move(other.deleter_);
owns_ = other.owns_;
other.owns_ = false;
}
return *this;
}

// 访问资源
Resource get() const noexcept {
return resource_;
}

// 释放所有权
Resource release() noexcept {
owns_ = false;
return resource_;
}

// 重置资源
void reset(Resource new_resource = Resource{}) noexcept {
if (owns_) {
deleter_(resource_);
}
resource_ = new_resource;
owns_ = true;
}

// 交换
void swap(RAIIWrapper& other) noexcept {
std::swap(resource_, other.resource_);
std::swap(deleter_, other.deleter_);
std::swap(owns_, other.owns_);
}

// 状态查询
explicit operator bool() const noexcept {
return owns_;
}

bool owns() const noexcept {
return owns_;
}

// 重载运算符
decltype(auto) operator*() const {
return *resource_;
}

decltype(auto) operator->() const {
return resource_;
}

private:
Resource resource_;
Deleter deleter_;
bool owns_;
};

// 辅助函数,用于创建RAIIWrapper
template <typename Resource, typename Deleter>
RAIIWrapper<Resource, Deleter> makeRAII(Resource resource, Deleter deleter) {
return RAIIWrapper<Resource, Deleter>(resource, deleter);
}

// 特化版本,用于原生指针
template <typename T>
RAIIWrapper<T*, std::default_delete<T>> makeRAII(T* ptr) {
return RAIIWrapper<T*, std::default_delete<T>>(ptr);
}

// 使用示例
void useRAIIWrapper() {
// 管理文件
auto file = makeRAII(
std::fopen("test.txt", "w"),
[](FILE* f) { if (f) std::fclose(f); }
);

// 管理动态内存
auto memory = makeRAII(new int[10]);

// 管理互斥锁
std::mutex mtx;
auto lock = makeRAII(
&mtx,
[](std::mutex* m) { m->unlock(); }
);
lock.get()->lock();

// 使用资源...
}

文件处理器的高级实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
class FileHandler {
public:
// 构造函数:获取资源
FileHandler(const char* filename, const char* mode) {
file_ = std::fopen(filename, mode);
if (!file_) {
throw std::system_error(
errno,
std::generic_category(),
"Failed to open file"
);
}
}

// 移动构造函数
FileHandler(FileHandler&& other) noexcept
: file_(other.file_) {
other.file_ = nullptr;
}

// 析构函数:释放资源
~FileHandler() {
if (file_) {
std::fclose(file_);
file_ = nullptr;
}
}

// 禁止拷贝
FileHandler(const FileHandler&) = delete;
FileHandler& operator=(const FileHandler&) = delete;

// 移动赋值运算符
FileHandler& operator=(FileHandler&& other) noexcept {
if (this != &other) {
reset();
file_ = other.file_;
other.file_ = nullptr;
}
return *this;
}

// 提供文件操作接口
FILE* get() const noexcept {
return file_;
}

// 写入操作
size_t write(const char* data, size_t size) {
if (!file_) {
throw std::runtime_error("File not open");
}
return std::fwrite(data, 1, size, file_);
}

// 读取操作
size_t read(char* buffer, size_t size) {
if (!file_) {
throw std::runtime_error("File not open");
}
return std::fread(buffer, 1, size, file_);
}

// 定位操作
int seek(long offset, int origin) {
if (!file_) {
throw std::runtime_error("File not open");
}
return std::fseek(file_, offset, origin);
}

// 刷新操作
int flush() {
if (!file_) {
throw std::runtime_error("File not open");
}
return std::fflush(file_);
}

// 重置
void reset() noexcept {
if (file_) {
std::fclose(file_);
file_ = nullptr;
}
}

// 释放所有权
FILE* release() noexcept {
FILE* temp = file_;
file_ = nullptr;
return temp;
}

// 状态查询
explicit operator bool() const noexcept {
return file_ != nullptr;
}

bool is_open() const noexcept {
return file_ != nullptr;
}

private:
FILE* file_ = nullptr;
};

// 使用示例
void processFile(const char* inputFile, const char* outputFile) {
FileHandler input(inputFile, "r");
FileHandler output(outputFile, "w");

char buffer[1024];
size_t bytesRead;

while ((bytesRead = input.read(buffer, sizeof(buffer))) > 0) {
output.write(buffer, bytesRead);
}

// 函数结束时,input和output的析构函数会自动调用
// 确保两个文件都被正确关闭,即使在处理过程中发生异常
}

RAII与异常安全性的深度分析

RAII是实现异常安全的关键技术。当程序抛出异常时,栈会被展开(stack unwinding),所有局部对象的析构函数都会被调用,确保资源被正确释放。

异常安全级别

  1. 基本保证:即使发生异常,程序也能保持在一致的状态,没有资源泄漏
  2. 强保证:如果发生异常,程序会回滚到操作前的状态,仿佛操作从未发生
  3. 无抛出保证:操作永远不会抛出异常

RAII实现强异常安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 基本异常安全
void unsafeSwap(std::string& a, std::string& b) {
std::string temp = a; // 如果分配失败,会抛出异常
a = b; // 如果分配失败,会抛出异常,此时temp已修改a
b = temp; // 如果分配失败,会抛出异常,此时a已修改为b
}

// 使用RAII实现强异常安全
void safeSwap(std::string& a, std::string& b) {
std::string temp = a; // 复制a
// 如果上面的操作失败,什么都没变,强保证

try {
a = b; // 复制b到a
// 如果上面的操作失败,temp仍然保存着原来的a,b不变

b = std::move(temp); // 移动temp到b
// 如果上面的操作失败,a已经是原来的b,b不变,temp可能被部分移动
} catch (...) {
// 发生异常时,恢复a的值
a = std::move(temp);
throw; // 重新抛出异常
}
}

// 更简洁的强异常安全实现(利用标准库的异常安全保证)
void simplerSafeSwap(std::string& a, std::string& b) {
std::string temp = std::move(a); // 移动a到temp,a变为空
a = std::move(b); // 移动b到a,b变为空
b = std::move(temp); // 移动temp到b,temp变为空
// 所有操作都是移动语义,不会抛出异常(假设std::string的移动操作不抛异常)
}

RAII与智能指针的异常安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 不安全的代码
void unsafeFunction() {
int* p = new int(42);
riskyOperation(); // 可能抛出异常
delete p; // 如果上面抛出异常,这里不会执行,导致内存泄漏
}

// 使用RAII(智能指针)的安全代码
void safeFunction() {
std::unique_ptr<int> p(new int(42));
riskyOperation(); // 可能抛出异常
// 如果上面抛出异常,p的析构函数会自动调用delete,不会导致内存泄漏
}

// 更安全的代码(使用make_unique)
void saferFunction() {
auto p = std::make_unique<int>(42);
riskyOperation(); // 可能抛出异常
// 同样安全,且更简洁
}

作用域守卫(Scope Guard)的高级实现

作用域守卫是RAII的一种特殊形式,用于在作用域结束时执行特定的清理操作。C++11及以上版本可以使用lambda表达式和模板实现通用的作用域守卫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// 通用作用域守卫
template <typename Func>
class ScopeGuard {
public:
explicit ScopeGuard(Func&& func)
: func_(std::forward<Func>(func)), active_(true) {}

ScopeGuard(ScopeGuard&& other) noexcept
: func_(std::move(other.func_)), active_(other.active_) {
other.active_ = false;
}

~ScopeGuard() {
if (active_) {
func_();
}
}

ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;

void dismiss() noexcept {
active_ = false;
}

private:
Func func_;
bool active_;
};

// 辅助函数,用于自动推导模板参数
template <typename Func>
ScopeGuard<Func> makeScopeGuard(Func&& func) {
return ScopeGuard<Func>(std::forward<Func>(func));
}

// 宏,用于创建作用域守卫(模仿Boost.ScopeExit)
#define SCOPE_EXIT \
auto CONCAT(scope_guard_, __LINE__) = makeScopeGuard([&]()

#define CONCAT(a, b) CONCAT_IMPL(a, b)
#define CONCAT_IMPL(a, b) a##b

// 使用示例
void useScopeGuard() {
FILE* file = std::fopen("test.txt", "w");
if (!file) {
return;
}

// 创建作用域守卫,确保文件关闭
SCOPE_EXIT {
if (file) {
std::fclose(file);
std::cout << "File closed by scope guard" << std::endl;
}
});

// 使用文件...
std::fprintf(file, "Hello, Scope Guard!");

// 函数结束时,作用域守卫会自动执行,关闭文件
}

// 另一个示例:事务回滚
void processTransaction(Database& db) {
db.beginTransaction();

// 创建作用域守卫,确保事务回滚
auto rollbackGuard = makeScopeGuard([&db]() {
db.rollbackTransaction();
std::cout << "Transaction rolled back" << std::endl;
});

// 执行数据库操作...

// 如果所有操作成功,取消回滚
rollbackGuard.dismiss();
db.commitTransaction();
std::cout << "Transaction committed" << std::endl;
}

RAII与移动语义的深度结合

C++11引入的移动语义极大地增强了RAII的能力,使得资源可以在不同对象之间高效转移,而不需要复制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// 支持移动语义的RAII类
class NetworkConnection {
public:
NetworkConnection(const char* host, int port) {
// 建立网络连接
sock_ = socket(AF_INET, SOCK_STREAM, 0);
if (sock_ < 0) {
throw std::system_error(
errno,
std::generic_category(),
"Failed to create socket"
);
}

// 设置地址
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(AF_INET, host, &addr.sin_addr) <= 0) {
close(sock_);
throw std::system_error(
errno,
std::generic_category(),
"Invalid address"
);
}

// 连接服务器
if (connect(sock_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
close(sock_);
throw std::system_error(
errno,
std::generic_category(),
"Failed to connect"
);
}

std::cout << "Connected to " << host << ":" << port << std::endl;
}

// 移动构造函数
NetworkConnection(NetworkConnection&& other) noexcept
: sock_(other.sock_) {
other.sock_ = -1; // 标记为无效
std::cout << "Connection moved" << std::endl;
}

// 析构函数
~NetworkConnection() {
if (sock_ >= 0) {
close(sock_);
std::cout << "Connection closed" << std::endl;
}
}

// 禁止拷贝
NetworkConnection(const NetworkConnection&) = delete;
NetworkConnection& operator=(const NetworkConnection&) = delete;

// 移动赋值运算符
NetworkConnection& operator=(NetworkConnection&& other) noexcept {
if (this != &other) {
// 关闭当前连接
if (sock_ >= 0) {
close(sock_);
}

// 转移连接
sock_ = other.sock_;
other.sock_ = -1;

std::cout << "Connection assigned via move" << std::endl;
}
return *this;
}

// 发送数据
ssize_t send(const void* data, size_t size) {
if (sock_ < 0) {
throw std::runtime_error("Not connected");
}
return ::send(sock_, data, size, 0);
}

// 接收数据
ssize_t recv(void* buffer, size_t size) {
if (sock_ < 0) {
throw std::runtime_error("Not connected");
}
return ::recv(sock_, buffer, size, 0);
}

// 状态查询
explicit operator bool() const noexcept {
return sock_ >= 0;
}

bool is_connected() const noexcept {
return sock_ >= 0;
}

private:
int sock_ = -1;
};

// 使用移动语义
NetworkConnection createConnection() {
return NetworkConnection("localhost", 8080); // 返回值优化
}

void useConnection() {
NetworkConnection conn = createConnection(); // 移动构造

// 使用连接...

// 转移连接所有权
NetworkConnection conn2 = std::move(conn); // 现在conn2拥有连接,conn变为无效

// 使用conn2...

// 函数结束时,只有conn2的析构函数会关闭连接
}

RAII与线程安全的深度集成

RAII可以与互斥锁结合,实现线程安全的资源访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// 线程安全的共享数据
class ThreadSafeData {
public:
ThreadSafeData(int value) : value_(value) {}

// 线程安全的访问
int get() const {
std::lock_guard<std::mutex> lock(mutex_);
return value_;
}

// 线程安全的修改
void set(int value) {
std::lock_guard<std::mutex> lock(mutex_);
value_ = value;
}

// 线程安全的修改(使用函数对象)
template <typename Func>
auto modify(Func&& func) {
std::lock_guard<std::mutex> lock(mutex_);
return func(value_);
}

private:
mutable std::mutex mutex_;
int value_;
};

// 使用示例
void useThreadSafeData() {
ThreadSafeData data(42);

// 启动多个线程修改数据
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([&data, i]() {
data.modify([i](int& value) {
value += i;
return value;
});
});
}

for (auto& thread : threads) {
thread.join();
}

std::cout << "Final value: " << data.get() << std::endl;
}

// 高级线程安全RAII包装器
template <typename T>
class ThreadSafeWrapper {
public:
template <typename... Args>
ThreadSafeWrapper(Args&&... args)
: data_(std::forward<Args>(args)...) {}

// 线程安全的访问
template <typename Func>
auto withLock(Func&& func) const {
std::lock_guard<std::mutex> lock(mutex_);
return func(data_);
}

// 线程安全的修改
template <typename Func>
auto withLockMut(Func&& func) {
std::lock_guard<std::mutex> lock(mutex_);
return func(data_);
}

private:
T data_;
mutable std::mutex mutex_;
};

// 使用示例
void useThreadSafeWrapper() {
ThreadSafeWrapper<std::vector<int>> safeVector({1, 2, 3, 4, 5});

// 线程安全的读取
int sum = safeVector.withLock([](const std::vector<int>& v) {
return std::accumulate(v.begin(), v.end(), 0);
});

// 线程安全的修改
safeVector.withLockMut([](std::vector<int>& v) {
v.push_back(6);
v.push_back(7);
});

// 线程安全的读取
safeVector.withLock([](const std::vector<int>& v) {
for (int num : v) {
std::cout << num << " ";
}
std::cout << std::endl;
});
}

RAII的最佳实践与性能优化

  1. 始终使用RAII管理资源

    • 对于任何需要手动释放的资源,都应该使用RAII包装器
    • 避免手动调用释放函数,减少资源泄漏的风险
  2. 优先使用标准库的RAII工具

    • 使用std::unique_ptrstd::shared_ptr管理内存
    • 使用std::lock_guardstd::unique_lock管理互斥锁
    • 使用std::fstream管理文件
    • 使用std::thread管理线程
  3. 实现自定义RAII类时的注意事项

    • 禁止拷贝:对于独占资源的RAII类,应该禁止拷贝操作
    • 支持移动:对于需要转移所有权的RAII类,应该支持移动操作
    • 异常安全:构造函数应该在获取资源失败时抛出异常,析构函数不应该抛出异常
    • 状态管理:应该跟踪资源的所有权状态,避免重复释放
    • 资源验证:在构造函数中应该验证资源获取是否成功
  4. 使用作用域控制资源生命周期

    • 通过局部变量的作用域控制资源的生命周期
    • 使用临时作用域精确控制资源的释放时机
  5. 结合移动语义提高性能

    • 对于大型资源,使用移动语义减少拷贝开销
    • 实现移动构造函数和移动赋值运算符
    • 使用std::move转移资源所有权
  6. 使用作用域守卫处理复杂的清理逻辑

    • 对于需要在作用域结束时执行的清理操作,使用作用域守卫
    • 支持条件清理(如事务回滚)
  7. 测试异常安全性

    • 确保在异常情况下资源能够被正确释放
    • 测试各种异常场景,确保程序状态一致
  8. 文档化资源管理策略

    • 明确记录RAII类的资源管理策略
    • 说明拷贝和移动语义的行为
  9. 性能优化策略

    • 内联析构函数:对于简单的RAII类,析构函数可以内联,减少函数调用开销
    • 小对象优化:对于小型RAII对象,考虑使用小对象优化,减少堆分配
    • 避免虚函数:对于性能关键的RAII类,避免使用虚函数,减少运行时开销
    • 使用 noexcept:对于不会抛出异常的移动操作,使用noexcept标记,提高性能

RAII的核心价值

RAII不仅仅是一种技术,更是一种设计哲学,它将资源管理的责任交给了对象的生命周期管理,使得代码更加简洁、安全、可维护。通过RAII,我们可以:

  • 消除资源泄漏:确保资源在不再需要时被正确释放
  • 提高异常安全性:即使在异常情况下也能保持程序的一致性
  • 简化代码:将资源管理逻辑集中在一处,提高代码可读性
  • 增强可维护性:减少手动资源管理的错误,提高代码质量
  • 促进良好的设计:鼓励将资源管理与业务逻辑分离

在现代C++中,RAII已经成为资源管理的标准做法,与智能指针、移动语义等特性相结合,为C++程序员提供了强大、安全、高效的资源管理工具。掌握RAII的精髓,是成为优秀C++程序员的必备条件。
// 自动解锁
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

#### RAII的最佳实践

1. **始终使用RAII管理资源**:对于任何需要手动释放的资源,都应该使用RAII
2. **遵循零规则**:如果类不需要手动管理资源,不要定义析构函数、拷贝构造函数和移动构造函数
3. **遵循三/五规则**:如果类需要手动管理资源,应该正确实现析构函数、拷贝构造函数、移动构造函数、拷贝赋值运算符和移动赋值运算符
4. **使用智能指针**:对于内存资源,优先使用标准库的智能指针
5. **避免裸指针**:尽量减少使用裸指针,特别是作为函数参数和返回值
6. **明确资源所有权**:在设计API时,明确资源的所有权转移语义
7. **使用`noexcept`**:对于析构函数和移动操作,使用`noexcept`标记,提高性能和异常安全性
8. **初始化成员变量**:在声明时初始化成员变量,避免未初始化的资源指针

**RAII的设计原则**:
- **单一职责**:每个RAII类只管理一种资源
- **资源获取的原子性**:构造函数应该要么成功获取资源,要么抛出异常,不应该留下部分初始化的对象
- **资源释放的安全性**:析构函数不应该抛出异常,否则会导致程序终止
- **移动语义的支持**:对于需要转移资源所有权的场景,应该支持移动操作

## 资源管理工具

### 标准库中的RAII工具

C++标准库提供了多种RAII工具,用于管理各种资源:

#### `std::lock_guard`

`std::lock_guard`是一个互斥锁的RAII包装器,在构造时锁定互斥锁,在析构时自动解锁,确保即使发生异常也能正确释放锁。

```cpp
#include <mutex>
#include <vector>

std::mutex dataMutex;
std::vector<int> sharedData;

void addData(int value) {
std::lock_guard<std::mutex> lock(dataMutex); // 构造时锁定
sharedData.push_back(value);
// 析构时自动解锁
}

std::lock_guard的实现原理

  • 构造函数接受一个互斥锁引用,并立即调用lock()方法
  • 析构函数调用互斥锁的unlock()方法
  • 禁止拷贝和移动,确保锁的所有权不被转移

std::unique_lock

std::unique_lock是一个更灵活的互斥锁RAII包装器,支持延迟锁定、超时锁定和手动锁定/解锁操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <mutex>
#include <chrono>
#include <thread>

std::mutex mtx;

void tryLockWithTimeout() {
// 延迟锁定
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);

// 尝试锁定
if (lock.try_lock()) {
std::cout << "Lock acquired immediately" << std::endl;
} else {
std::cout << "Lock not available, trying with timeout..." << std::endl;

// 尝试超时锁定
if (lock.try_lock_for(std::chrono::seconds(1))) {
std::cout << "Lock acquired after timeout" << std::endl;
} else {
std::cout << "Failed to acquire lock" << std::endl;
return;
}
}

// 手动解锁
lock.unlock();
std::cout << "Lock released manually" << std::endl;

// 重新锁定
lock.lock();
std::cout << "Lock reacquired" << std::endl;

// 析构时会再次解锁
}

std::unique_lock的特点

  • 大小通常为两个指针(16字节),比std::lock_guard
  • 支持移动操作,可以在不同作用域间转移锁的所有权
  • 提供owns_lock()方法检查是否持有锁
  • 可以与条件变量配合使用

std::scoped_lock

C++17引入的std::scoped_lock可以同时锁定多个互斥锁,使用死锁避免算法(如顺序锁定)确保不会发生死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <mutex>
#include <iostream>

std::mutex mtx1, mtx2, mtx3;

void processData() {
// 同时锁定多个互斥锁
std::scoped_lock lock(mtx1, mtx2, mtx3);

// 临界区代码
std::cout << "All locks acquired" << std::endl;
// 处理共享数据

// 析构时自动释放所有锁
}

std::scoped_lock的优势

  • 避免手动锁定多个互斥锁时可能出现的死锁
  • 语法简洁,一行代码锁定任意数量的互斥锁
  • 自动处理所有锁的释放,即使发生异常

std::unique_ptrstd::shared_ptr

智能指针也是标准库中的RAII工具,用于管理动态内存:

  • std::unique_ptr:独占所有权的智能指针,适用于大多数场景
  • std::shared_ptr:共享所有权的智能指针,适用于需要多个所有者的场景
  • std::weak_ptr:不增加引用计数的智能指针,用于解决循环引用问题

其他RAII工具

  • std::fstream:管理文件流,自动打开和关闭文件
  • std::thread:管理线程,析构时如果线程仍在运行会调用std::terminate()
  • std::lock_guard 的变体:如std::lock_guard<std::recursive_mutex>
  • std::condition_variable:与互斥锁配合使用,管理线程同步

内存管理最佳实践

1. 优先使用智能指针

推荐做法

  • 对于独占所有权的场景,使用std::unique_ptr
  • 对于共享所有权的场景,使用std::shared_ptr
  • 避免使用原始指针管理动态内存

性能考虑

  • std::unique_ptr的性能开销极小,与原始指针接近
  • std::shared_ptr有引用计数开销,适用于确实需要共享所有权的场景

2. 使用std::make_uniquestd::make_shared

为什么推荐

  • 异常安全:避免在分配内存和构造对象之间发生异常导致的内存泄漏
  • 性能优化std::make_shared只进行一次内存分配(同时分配对象和控制块)
  • 代码简洁:语法更简洁,减少重复代码

使用示例

1
2
3
4
5
6
7
// 推荐
auto p1 = std::make_unique<int>(42);
auto p2 = std::make_shared<std::string>("Hello");

// 不推荐
std::unique_ptr<int> p3(new int(42));
std::shared_ptr<std::string> p4(new std::string("Hello"));

3. 避免循环引用

循环引用的危害

  • 导致内存泄漏,因为引用计数永远不会减为零
  • 增加内存使用,延缓资源释放

解决方案

  • 使用std::weak_ptr打破循环引用
  • 重新设计数据结构,避免循环依赖

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Node {
public:
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 使用weak_ptr避免循环引用

~Node() {
std::cout << "Node destroyed" << std::endl;
}
};

void createList() {
auto n1 = std::make_shared<Node>();
auto n2 = std::make_shared<Node>();

n1->next = n2;
n2->prev = n1;

// 不会导致内存泄漏,两个节点都会被正确销毁
}

4. 合理使用RAII

RAII的应用场景

  • 内存管理(智能指针)
  • 文件操作(std::fstream
  • 同步原语(std::lock_guard
  • 网络连接(自定义RAII包装器)
  • 数据库连接(自定义RAII包装器)

RAII设计原则

  • 每个RAII类只管理一种资源
  • 构造函数获取资源,析构函数释放资源
  • 禁止拷贝或正确实现拷贝语义
  • 支持移动语义以提高性能

5. 避免内存泄漏

常见内存泄漏原因

  • 未配对使用newdelete
  • 异常导致的资源未释放
  • 循环引用(std::shared_ptr
  • 全局对象的资源未释放

预防措施

  • 使用智能指针和RAII
  • 定期使用内存分析工具检测
  • 编写单元测试验证资源释放
  • 采用静态分析工具检查潜在问题

6. 避免悬空指针和野指针

悬空指针:指向已释放内存的指针
野指针:未初始化或指向无效内存的指针

预防措施

  • 使用智能指针自动管理生命周期
  • 释放内存后将指针置为nullptr
  • 总是初始化指针
  • 采用现代C++风格,减少裸指针的使用

7. 内存安全编程

最佳实践

  • 使用标准库容器代替手动内存管理
  • 避免使用reinterpret_cast进行危险的类型转换
  • 使用std::span(C++20)处理数组,避免越界访问
  • 采用边界检查工具(如AddressSanitizer)

内存优化

内存分配策略

内存分配是影响程序性能的重要因素,合理的分配策略可以显著提高程序效率:

  1. 减少动态内存分配

    • 优先使用栈分配和静态分配
    • 对于小对象,使用栈分配
    • 对于固定大小的全局数据,使用静态分配
  2. 批量分配

    • 使用std::vector等容器,一次性分配足够的内存
    • 预留空间(reserve)减少重新分配
    • 批量处理数据,减少分配次数
  3. 内存池

    • 对于频繁分配和释放的小对象,使用内存池
    • 预分配大块内存,然后分割成小块使用
    • 减少内存碎片,提高分配速度
  4. 对象池

    • 对于固定大小的对象,使用对象池
    • 复用对象,避免频繁构造和析构
    • 适用于对象创建成本高的场景
  5. 多态内存资源(C++17+)

    • 使用std::pmr命名空间中的内存资源管理工具
    • 自定义内存分配策略
    • 适用于需要特殊内存管理的场景

内存池实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class MemoryPool {
public:
MemoryPool(size_t blockSize, size_t blockCount)
: blockSize(blockSize), blockCount(blockCount) {
// 分配大块内存
pool = static_cast<char*>(std::malloc(blockSize * blockCount));
if (!pool) {
throw std::bad_alloc{};
}

// 初始化自由块链表
for (size_t i = 0; i < blockCount; ++i) {
char* block = pool + i * blockSize;
*reinterpret_cast<char**>(block) = freeList;
freeList = block;
}
}

~MemoryPool() {
std::free(pool);
}

// 分配内存
void* allocate() {
if (!freeList) {
throw std::bad_alloc{};
}

void* block = freeList;
freeList = *reinterpret_cast<char**>(freeList);
return block;
}

// 释放内存
void deallocate(void* ptr) {
if (!ptr) return;

// 将块放回自由链表
*reinterpret_cast<char**>(ptr) = freeList;
freeList = static_cast<char*>(ptr);
}

// 禁止拷贝和移动
MemoryPool(const MemoryPool&) = delete;
MemoryPool& operator=(const MemoryPool&) = delete;
MemoryPool(MemoryPool&&) = delete;
MemoryPool& operator=(MemoryPool&&) = delete;

private:
size_t blockSize;
size_t blockCount;
char* pool;
char* freeList;
};

// 使用内存池
MemoryPool pool(sizeof(int), 100);
int* p = static_cast<int*>(pool.allocate());
*p = 42;
pool.deallocate(p);

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

C++17引入的std::pmr命名空间提供了灵活的内存资源管理机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <memory_resource>
#include <vector>
#include <string>

void usePolymorphicMemoryResources() {
// 1. 使用单调缓冲区资源(内存池)
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);
}

// 2. 使用同步内存资源(线程安全)
std::pmr::synchronized_pool_resource syncPool;
std::pmr::vector<std::pmr::string> strings(&syncPool);
strings.push_back("Hello");
strings.push_back("World");

// 3. 使用未同步内存资源(单线程优化)
std::pmr::unsynchronized_pool_resource unsyncPool;
std::pmr::vector<double> doubles(&unsyncPool);
for (int i = 0; i < 50; ++i) {
doubles.push_back(i * 1.1);
}
}

内存对齐

内存对齐是指变量或对象在内存中的起始地址必须是某个值的倍数,正确的对齐可以提高内存访问速度:

内存对齐的原因

  • 硬件通常按字长访问内存,未对齐的访问需要多次内存操作
  • 某些指令集要求数据必须对齐
  • 提高缓存利用率

C++中的内存对齐控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 检查类型的对齐要求
std::cout << "alignof(char): " << alignof(char) << std::endl;
std::cout << "alignof(int): " << alignof(int) << std::endl;
std::cout << "alignof(double): " << alignof(double) << std::endl;

// 指定变量的对齐方式
alignas(16) int x; // x对齐到16字节边界

// 指定结构体的对齐方式
struct alignas(32) MyStruct {
int a;
double b;
char c;
};

std::cout << "sizeof(MyStruct): " << sizeof(MyStruct) << std::endl;
std::cout << "alignof(MyStruct): " << alignof(MyStruct) << std::endl;

// 对齐的联合体
union alignas(64) MyUnion {
char data[64];
double value;
};

内存对齐的性能影响

  • 对齐的数据访问速度更快
  • 但可能会增加内存使用(填充)
  • 对于频繁访问的数据结构,对齐是值得的

内存使用分析

定期分析内存使用情况,找出优化机会:

分析工具

  1. Valgrind:Linux下的内存分析工具套件

    • Memcheck:检测内存泄漏、越界访问等
    • Massif:分析内存使用和分配情况
    • Cachegrind:分析缓存使用情况
  2. AddressSanitizer:Google开发的内存错误检测工具

    • 检测内存泄漏、越界访问、使用已释放内存等
    • 集成到主流编译器(GCC、Clang、MSVC)
    • 运行时开销比Valgrind小
  3. Visual Studio Memory Profiler:Windows下的内存分析工具

    • 内存使用快照
    • 内存分配跟踪
    • 内存泄漏检测
  4. Intel VTune Profiler:性能分析工具,包括内存分析

    • 内存访问模式分析
    • 缓存命中率分析
    • 内存带宽分析

分析步骤

  1. 识别内存热点:找出消耗内存最多的部分
  2. 分析分配模式:识别频繁的小内存分配
  3. 检测内存泄漏:确保所有资源都被正确释放
  4. 优化内存使用:根据分析结果进行优化

内存优化的高级技术

  1. 对象布局优化

    • 按大小排序成员变量,减少填充
    • 使用位域节省空间
    • 考虑使用std::compressed_pair减少空基类开销
  2. 字符串优化

    • 使用短字符串优化(SSO)的字符串实现
    • 对于固定长度的字符串,使用std::array<char, N>
    • 避免不必要的字符串拷贝
  3. 容器优化

    • 为容器预留足够空间(reserve
    • 选择合适的容器类型
    • 考虑使用自定义分配器
  4. 缓存优化

    • 数据局部性:将相关数据放在一起
    • 避免伪共享:对齐数据避免缓存行冲突
    • 预取数据:使用__builtin_prefetch等指令
  5. 内存映射

    • 使用mmap(POSIX)或MapViewOfFile(Windows)处理大文件
    • 减少内存拷贝,提高I/O性能
    • 适用于需要随机访问大文件的场景

内存映射示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
#include <fstream>
#include <vector>

// 简单的内存映射包装器(仅作示例)
class MemoryMappedFile {
public:
MemoryMappedFile(const char* filename) {
// 在实际实现中,这里会使用平台特定的内存映射API
// 例如:mmap(POSIX)或MapViewOfFile(Windows)
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (file) {
size = file.tellg();
data = new char[size];
file.seekg(0);
file.read(data, size);
}
}

~MemoryMappedFile() {
delete[] data;
}

const char* getData() const { return data; }
size_t getSize() const { return size; }

private:
char* data = nullptr;
size_t size = 0;
};

// 使用内存映射读取大文件
void processLargeFile(const char* filename) {
MemoryMappedFile mmf(filename);
const char* data = mmf.getData();
size_t size = mmf.getSize();

// 直接访问内存映射的数据
// 无需手动管理缓冲区,提高I/O性能

std::cout << "File size: " << size << " bytes" << std::endl;
if (size > 0) {
std::cout << "First byte: " << static_cast<int>(data[0]) << std::endl;
}
}

内存管理的未来发展

C++标准委员会持续改进内存管理机制,未来的发展方向包括:

  1. 更智能的内存分配器:自适应分配策略,根据使用模式自动调整
  2. 内存安全:减少内存错误,提高程序安全性
  3. 垃圾回收:可选的垃圾回收机制,用于特定场景
  4. 更灵活的内存资源管理:扩展std::pmr功能
  5. 硬件加速:利用硬件特性提高内存管理效率

作为C++程序员,了解这些发展趋势,掌握先进的内存管理技术,对于编写高性能、可靠的C++程序至关重要。

示例:内存管理的综合应用

自定义智能指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
template<typename T>
class SmartPtr {
public:
explicit SmartPtr(T* ptr = nullptr) : ptr(ptr) {}

~SmartPtr() {
delete ptr;
}

// 禁止拷贝
SmartPtr(const SmartPtr&) = delete;
SmartPtr& operator=(const SmartPtr&) = delete;

// 允许移动
SmartPtr(SmartPtr&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr;
}

SmartPtr& operator=(SmartPtr&& other) noexcept {
if (this != &other) {
delete ptr;
ptr = other.ptr;
other.ptr = nullptr;
}
return *this;
}

// 重载运算符
T& operator*() const {
return *ptr;
}

T* operator->() const {
return ptr;
}

// 检查是否为空
explicit operator bool() const {
return ptr != nullptr;
}

private:
T* ptr;
};

// 使用示例
void useSmartPtr() {
SmartPtr<int> p(new int(42));
std::cout << *p << std::endl;

SmartPtr<int> p2 = std::move(p);
std::cout << *p2 << std::endl;

// 不需要手动释放内存
}

资源管理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class DatabaseConnection {
public:
DatabaseConnection(const std::string& connectionString) {
// 建立数据库连接
std::cout << "Connecting to database: " << connectionString << std::endl;
// 模拟连接过程
connected = true;
}

~DatabaseConnection() {
// 关闭数据库连接
if (connected) {
std::cout << "Disconnecting from database" << std::endl;
connected = false;
}
}

// 禁止拷贝
DatabaseConnection(const DatabaseConnection&) = delete;
DatabaseConnection& operator=(const DatabaseConnection&) = delete;

// 允许移动
DatabaseConnection(DatabaseConnection&& other) noexcept : connected(other.connected) {
other.connected = false;
}

DatabaseConnection& operator=(DatabaseConnection&& other) noexcept {
if (this != &other) {
// 关闭当前连接
if (connected) {
std::cout << "Disconnecting from database" << std::endl;
}
// 移动连接
connected = other.connected;
other.connected = false;
}
return *this;
}

// 数据库操作
void executeQuery(const std::string& query) {
if (connected) {
std::cout << "Executing query: " << query << std::endl;
} else {
throw std::runtime_error("Not connected to database");
}
}

private:
bool connected;
};

// 使用示例
void useDatabase() {
try {
DatabaseConnection db("server=localhost;database=test");
db.executeQuery("SELECT * FROM users");

// 连接会在db对象销毁时自动关闭
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}

总结

内存与资源管理是C++编程中的重要主题,直接关系到程序的性能和稳定性。通过合理使用动态内存分配、智能指针和RAII等技术,可以有效地管理内存和资源,避免内存泄漏、悬空指针等问题。

在现代C++中,应该优先使用智能指针和RAII来管理内存和资源,减少手动内存管理的错误。同时,合理的内存分配策略和内存优化技术可以提高程序的性能和内存使用效率。

通过本章的学习,读者应该掌握C++中的内存管理和资源管理技术,能够编写更加安全、高效的C++程序。