智能指针(c++11)
智能指针是C++中用于自动管理动态内存的RAII(资源获取即初始化)机制。文章介绍了C++98到C++11智能指针的发展历程,包括auto_ptr(已废弃)、unique_ptr(禁止拷贝)、shared_ptr(引用计数)和weak_ptr(解决循环引用)。其中重点分析了shared_ptr通过引用计数实现安全拷贝,以及weak_ptr如何避免循环引用导致的内存泄漏。文章还演示了自定义删除器的使
什么是智能指针
如下代码存在什么问题
#include <iostream>
#include <vector>
#include <string>
using namespace std;
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr = nullptr)
:_ptr(ptr)
{ }
~SmartPtr()
{
if (_ptr) delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void func()
{
pair<string, string>* p1 = new pair<string, string>;
//pair<string, string>* p2 = new pair<string, string>;
try
{
div();
}
catch (...)
{
delete p1;
throw;
}
delete p1;
}
int main()
{
try
{
func();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
上面的指针只有一个,还好管理,如果指针有三个呢?如果第二个开辟失败,那就得释放第一个。这里要分类讨论,每种情况还都不一样。那如果有10个呢?这要套多少个try..catch语句。像这种情况的内存泄漏,是很可怕的。
pair<string, string>* p1 = new pair<string, string>;
pair<string, string>* p2 = new pair<string, string>;
pair<string, string>* p3 = new pair<string, string>;
使用智能指针管理指针
#include <iostream>
#include <vector>
#include <string>
using namespace std;
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr = nullptr)
:_ptr(ptr)
{ }
~SmartPtr()
{
if (_ptr) delete _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void func()
{
//使用SmartPtr类对象,来管理指针的生命周期 只要出了作用域 自动调用析构函数 释放内存
SmartPtr<pair<string, string>> sp1(new pair<string, string>{ "byte","字节" });
SmartPtr<string> sp2(new string{ "byte" });
cout << sp1->first << "-" << sp1->second << endl;
cout << *sp2 << endl;
div();
}
int main()
{
try
{
func();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术。
智能指针采取RAII思想管理指针变量,出了作用域自动调用析构函数,释放内存。SmartPtr是模拟智能指针定义的类,他的一切功能都是模拟指针实现的。像指针的operator*和operator->都要重载实现,这里拷贝构造和赋值重载要实现浅拷贝功能,和指针一样,指针就是浅拷贝。
Boost库
Boost 库是一个由 C++ 社区驱动、开源且免费的高质量 C++ 通用库集合,旨在扩展 C++ 标准库的功能,填补标准库在特定领域的空白,并为 C++ 开发者提供工业级、可移植、高效的工具组件。它被广泛视为 “C++ 标准库的试验场”—— 许多 Boost 库(如shared_ptr、thread、regex等)已被正式纳入 C++11 及后续标准,成为 C++ 标准的核心组成部分
智能指针发展历史:
| auto_ptr | c++98 | 吐槽最多,很多地方禁止使用 |
| unique_ptr | c++11 | 来自Boost库 |
| shared_ptr | c++11 | 来自Boost库 |
| weak_ptr | c++11 | 来自Boost库 |
Boost库智能指针:
| Boost库 | c++标准 |
| scoped_ptr | unique_ptr |
| shared_ptr | shared_ptr |
| weak_ptr | weak_ptr |
auto_ptr:
auto_ptr这一版本的智能指针,实现最简单,毛病最严重,会产生悬空问题。
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{ }
~auto_ptr()
{
if (_ptr) delete _ptr;
}
T& operator*() //返回引用
{
return *_ptr;
}
T* operator->() //返回指针
{
return _ptr;
}
//拷贝构造 管理权转移 会导致悬空
auto_ptr(auto_ptr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
private:
T* _ptr;
};
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "int a = 0" << endl;
}
~A()
{
cout << "~A()" << endl;
}
//private:
int _a;
};
void auto_ptr_test()
{
//c++98 很多地方 明确禁止使用auto_ptr
byte::auto_ptr<A> ap1(new A(1));
byte::auto_ptr<A> ap2(new A(2));
//管理权转移 拷贝时 会把拷贝对象的资源管理权转移给拷贝对象
//导致被拷贝对象悬空 访问就会出问题
byte::auto_ptr<A> ap3(ap1);
//崩溃
ap1->_a++;
}
这里没有模拟实现operator=,他和拷贝构造是一样的。都会造成被拷贝对象悬空,内存泄漏。于是我们的unique_ptr就诞生了,他解决悬空问题。
unique_ptr:
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{
}
~unique_ptr()
{
if (_ptr) delete _ptr;
}
T& operator*() //返回引用
{
return *_ptr;
}
T* operator->() //返回指针
{
return _ptr;
}
//防拷贝
unique_ptr(unique_ptr<T>& up) = delete;
unique_ptr<T>& operator=(unique_ptr<T>& up) = delete;
private:
T* _ptr;
};
他的解决方案就是禁止使用拷贝构造和赋值,和他的名字一样,unique_ptr,唯一的指针,不允许拷贝和赋值。但是,指针的拷贝和赋值是再正常不过的需求了,总会需要的。于是c++委员会就又搞成了个shared_ptr,允许你拷贝构造和赋值。
shared_ptr:
shared_ptr采取一种指针变量记录被拷贝和赋值的次数,只有这个变量指向的次数变成0才可以析构称为引用计数,和java的静态变量count类似。但不过这里不可以采取静态变量,因为静态变量属于每个对象和类,我们只有被拷贝/赋值的对象才能记录。
template<class T>
class shared_ptr
{
public:
//function<void<T*>> _del 包装器
template<class D>
shared_ptr(T* ptr,D del = _del)
:_ptr(ptr)
,_pcount(new int(1))
,_del(del)
{
}
shared_ptr(T* ptr)
:_ptr(ptr)
, _pcount(new int(1))
{
}
~shared_ptr()
{
if (--(*_pcount) == 0)
{
_del(_ptr);
delete _pcount;
}
}
T& operator*() //返回引用
{
return *_ptr;
}
T* operator->() //返回指针
{
return _ptr;
}
//
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
,_pcount(sp._pcount)
{
++(*_pcount);
}
shared_ptr<T>& operator=(shared_ptr<T>& sp)
{
//防止自己给自己赋值
if (_ptr != sp._ptr)
{
//引用计数为0 析构自己
if (--(*_pcount) == 0)
{
delete _pcount;
delete _ptr;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
(*_pcount)++;//引用计数++
}
return *this;
}
int use_count()
{
return *_pcount;
}
T* get()
{
return _ptr;
}
private:
T* _ptr;
int* _pcount;
function<void(T*) > _del = [](T* ptr) {delete ptr; };
};
shared_ptr会自己释放掉被拷贝对象(ccount等于0),但是shared_ptr也不是十全十美,他会产生循环引用,导致内存泄漏。
struct Node
{
A _val;
byte::shared_ptr<Node> _next;
byte::shared_ptr<Node> _prev;
~Node()
{
cout << "~Node" << endl;
}
};
void cir_ref()
{
byte::shared_ptr<Node> sp1(new Node);
byte::shared_ptr<Node> sp2(new Node);
sp1->_next = sp2;
sp2->_prev = sp1;
}

类似于套娃,自己存储了个自己,因为count是2,导致并没有释放内存。那咋办呢?于是weak_ptr就产生了,弱指针,不会记录count的更迭。
weak_ptr:
template<class T>
class weak_ptr
{
public:
weak_ptr(T* ptr = nullptr)
:_ptr(ptr)
{}
//不显式定义析构函数
T& operator*() //返回引用
{
return *_ptr;
}
T* operator->() //返回指针
{
return _ptr;
}
weak_ptr(shared_ptr<T>& sp)
:_ptr(sp.get())
{}
weak_ptr<T>& operator=(shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
private:
T* _ptr;
};
循环引用的问题就是内部定义了个自己的类对象而已,导致count多加了。weak_ptr就不定义指针count变量,不参加对象的释放,只参与对象的管理。weak_ptr并不能算独立的智能指针,他是伴随shared_ptr而产生的,他的拷贝构造甚至都没有提供weak_ptr,但是却提供了shared_ptr版本的拷贝构造。所以weak_ptr其实使用往往都是和shared_ptr搭配使用。
struct Node
{
A _val;
byte::weak_ptr<Node> _next;
byte::weak_ptr<Node> _prev;
~Node()
{
cout << "~Node" << endl;
}
};
void cir_ref()
{
byte::shared_ptr<Node> sp1(new Node);
byte::shared_ptr<Node> sp2(new Node);
sp1->_next = sp2;
sp2->_prev = sp1;
}
删除器
shared_ptr和unique_ptr都用删除器,本质都是仿函数。shared_ptr的删除器设计在构造函数里,unique_ptr的删除器直接就设计在类模板里。

//仿函数 删除器
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
delete[] ptr;
}
};
void Del_test()
{
byte::shared_ptr<A> sp(new A);
byte::shared_ptr<A> sp1(new A[5], DeleteArray<A>());
byte::shared_ptr<A> sp2((A*)malloc(sizeof(A)), [](A* ptr) {free(ptr); });
byte::shared_ptr<FILE> sp3(fopen("SmartPtr_Test.cpp","r"),[](FILE* ptr) {fclose(ptr); });
}
int main()
{
Del_test();
return 0;
}
删除器在实际中运用比较多
智能指针总结
| 智能指针 | 版本 | 特性 |
| auto_ptr | c++98 | 建议不用 |
| unique_ptr | c++11 | 不使用拷贝和赋值场景,建议使用 |
| shared_ptr | c++11 | 几乎都可以使用,小心循环引用 |
| weak_ptr | c++11 | 解决shared_ptr循环引用问题,不能单独构建资源,根本没提供对应的构造 |
更多推荐



所有评论(0)