内存管理是 C/C++ 中一个常见的错误和 bug 来源。许多这些 bug 都来自动态内存分配和指针的使用。在程序中广泛使用动态内存分配,在对象间传递多个指针时,很容易忘记每个指针只能在正确时间执行一次 delete
操作。出错的后果很严重,多次释放动态分配的内存,可能会导致内存损坏或致命的运行时错误。忘记释放动态分配的内存时,会导致内存泄漏。
智能指针
智能指针来自于一个事实:把所有内容都放在堆栈上,可以避免大部分和内存相关的问题(简单的说,不要出现 new
就不用想着 delete
)。因为当堆栈变量离开作用域时,会自动销毁并清理。智能指针结合了堆栈变量的安全性和堆变量的灵活性。
有几种智能指针,最简单的智能指针类型对内存有唯一的所有权,当智能指针离开作用域时,会释放所引用的内存,例如 unique_ptr
。然而,指针的管理不仅仅是在指针离开作用域时就删除它们。有时,多个对象或代码包含了同一指针的多个副本。这个问题称为别名(aliasing)。为了正确释放所有内存,使用这个内存的最后一块代码应该对指针调用 delete
。
然而,往往很难知道哪一块代码最后使用了这块内存,有时候还依赖于运行时的输入。因此,一个更成熟的智能指针类型实现了引用计数以跟踪指针的所有者。当所有的拥有者都使用完指针时,引用计数降为0,此时智能指针对底层的普通指针调用 delete
。这就是后面提到的 shared_ptr
,它包含了引用计数。
过时的 auto_ptr
C++11 之前,标准库包含了智能指针的简单实现,称为 auto_ptr
。然而,auto_ptr
存在一些严重的缺点。比如,在 STL 容器(如vector
)中不能正常工作。C++已经正式废弃 auto_ptr
,使用 shared_ptr
和 unique_ptr
代替。
shared_ptr和unique_ptr的区别
shared_ptr
和 unique_ptr
的区别:
shared_ptr
是引用计数的智能指针- 可以有多个
shared_ptr
实例指向同一块动态分配的内存 - 最后一个
shared_ptr
离开作用域时,才释放内存 unique_prt
意味着所有权,单个unique_prt
离开作用域时,才会释放底层的内存
两个智能指针都需要包含 <memory>
头文件
永远不要把内存分配的结果赋值给普通指针,无论使用什么内存分配方法。
unique_ptr
作为一个经验法则,总将动态分配的对象保存在堆栈中的 unique_ptr
实例中。
考虑下面这样一个函数:
void leaky() {
// Bug! 内存从来没有被释放
Simple* mySimplePtr = new Simple();
mySimplePtr->go();
}
可能你会觉得下面的代码正确地释放了内存:
void leaky() {
Simple* mySimplePtr = new Simple();
mySimplePtr->go();