作者 Eaton
导语 在 C++ 中,内存管理是十分重要的问题,一不小心就会造成程序内存泄露,那么怎么避免呢?通过智能指针可以优雅地管理内存,让开发者只需要关注内存的申请,内存的释放则会被自动管理。在文章 开源微服务框架 TARS 之 基础组件 中已经简要介绍过,TARS 框架组件中没有直接使用 STL 库中的智能指针,而是实现了自己的智能指针。本文将会分别对 STL 库中的智能指针和 TarsCpp 组件中的智能指针进行对比分析,并详细介绍 TARS 智能指针的实现原理。
目录
智能指针
简介
在计算机程序中,泄露是常见的问题,包括内存泄露和资源泄露。其中资源泄露指的是系统的 socket
、文件描述符等资源在使用后,程序不再需要它们时没有得到释放;内存泄露指的是动态内存在使用后,程序不再需要它时没有得到释放。
内存泄露会使得程序占用的内存越来越多,而很大一部分往往是程序不再需要使用的。在 C++ 程序中,内存泄露常见于我们使用了 new
或者 malloc
申请动态存储区的内存,却忘了使用 delete
或者 free
去释放内存,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
随着计算机应用需求的日益增加,应用的设计与开发日趋复杂,开发人员在开发过程中处理的变量也越来越多。如何有效进行内存分配和释放、防止内存泄漏逐渐成为开发者面临的重要难题。为了解决忘记手动释放内存造成的内存泄露问题,智能指针诞生了。
常见的智能指针的使用场景,包括类中的成员变量(指针型)和普通的变量(指针型)。智能指针可以实现指针指向对象的共享,而无需关注动态内存的释放。通用实现技术是引用计数(Reference count),下一部分会介绍,简单讲就是将一个计数器与类指向的对象相关联,跟踪有多少个指针指向同一对象,新增一个指针指向该对象则计数器 +1
,减少一个则执行 -1
。
引用计数原理
引用计数是智能指针的一种通用实现技术,上图为大致流程,基本原理如下:
- 在每次创建类的新对象时,初始化指针并将引用计数置
1
; - 当对象作为另一对象的副本而创建时(复制构造函数),复制对应的指针并将引用计数
+1
; - 当对一个对象进行赋值时,赋值操作符
=
将左操作数所指对象的引用计数-1
,将右操作数所指对象的引用计数+1
; - 调用析构函数数,引用计数
-1
; - 上述操作中,引用计数减至
0
时,删除基础对象;
STL 库中的智能指针 shared_ptr
和 TARS 智能指针都使用了该引用计数原理,后面会进行介绍。
STL 库的智能指针
C++ 标准模板库 STL 中提供了四种指针 auto_ptr
, unique_ptr
, shared_ptr
, weak_ptr
。
auto_ptr
在 C++98 中提出,但其不能共享对象、不能管理数组指针,也不能放在容器中。因此在 C++11 中被摒弃,并提出 unique_ptr
来替代,支持管理数组指针,但不能共享对象。
shared_ptr
和 weak_ptr
则是 C++11 从标准库 Boost 中引入的两种智能指针。shared_ptr
用于解决多个指针共享一个对象的问题,但存在循环引用的问题,引入 weak_ptr
主要用于解决循环引用的问题。
接下来将详细介绍 shared_ptr
,关于其它智能指针的更多信息和用法请读者自行查阅。
shared_ptr
shared_ptr
解决了在多个指针间共享对象所有权的问题,最初实现于 Boost 库中,后来收录于 C++11 中,成为了标准的一部分。shared_ptr
的用法如下
#include <memory>
#include <iostream>
using namespace std;
class A
{
public:
A() {
};
~A()
{
cout << "A is destroyed" << endl;
}
};
int main()
{
shared_ptr<A> sptrA(new A);
cout << sptrA.use_count() << endl;
{
shared_ptr<A> cp_sptrA = sptrA;
cout << sptrA.use_count() << endl;
}
cout << sptrA.use_count() << endl;
return 0;
}
上述代码的意思是 cp_sptrA
声明并赋值后,引用计数增加 1
,cp_sptrA
销毁后引用计数 -1
,但是没有触发 A
的析构函数,在 sprtA
销毁后,引用计数变为 0
,才触发析构函数,实现内存的回收。执行结果如下
1
2
1
A is destroyed
shared_ptr
主要的缺陷是遇到循环引用时,将造成资源无法释放,下面给出一个示例:
#include <memory>
#include <iostream>
using namespace std;
class B;
class A
{
public:
A() : m_sptrB(nullptr) {
};
~A()
{
cout << " A is destroyed" << endl;
}
shared_ptr<B> m_sptrB;
};
class B
{
public:
B() : m_sptrA(nullptr) {
};
~B()