什么是智能指针
智能指针是C++中可以自动管理内存的工具,它可以在不手动释放资源时,确保对象能够正确地被销毁。
C++ 中智能指针和指针的区别是什么?
智能指针是类模板,指针是C/C++中一个变量类型,存放的是内存地址。
在使用上,智能指针和裸指针都支持解引用*,->等操作,但智能指针提供包含许多额外的函数。
C++共有4种智能指针,包括:
- unique_ptr:拥有独有对象所有权的智能指针,对于独有对象同一时间只有一个智能指针拥有它。
- shared_ptr:拥有共享对象所有权的智能指针,通过引用计数跟踪引用特定对象。当引用计数为0时,释放拥有的对象。
- weak_ptr:对shared_ptr的弱引用,访问时需要转换为shared_ptr;不控制对象的生命周期,配合shared_ptr用于解决shared_ptr循环引用问题。
- auto_ptr:C++11前的智能指针,在C++17时已废弃,无需了解
void UseRawPointer()
{
Song* pSong = new Song(L"Nothing on you");
//Use pSong...
delete pSong;
}
void UseSmartPointer()
{
unique_ptr<Song> song2(new Song(L"Nothing on You"));
// Use song2...
}
智能指针使用指向堆分配对象的原始指针进行初始化,初始化后,智能指针拥有原始指针。智能指针负责对原始指针的释放。
智能指针使用RAII机制,管理堆内存中的数据。当智能指针超过其作用域,或者抛出异常时,就会调用智能指针的析构函数,自动释放堆上的内存数据。
各个智能指针的特点
unique_ptr
unique_ptr通过指针占用并独占一个对象,并在unique_ptr离开作用域时释放该对象。
有两个场景会使用关联的删除器释放对象:
- 销毁了管理的unique_ptr对象
- 通过operator=或者reset()赋值给另一个unique_ptrs对象
unique_ptr只可移动,不支持复制。
unique_ptr与裸指针大小一致,在内存上无任何额外的消耗,和裸指针性能相当。
shared_ptr
支持复制、移动等操作。
内存占用上为裸指针的两倍,共有两个指针:
- element_type* _M_ptr:指向所管理的资源
- __shared_count<_Lp> _M_refcount:指向维护的引用计数,包括引用计数和弱引用计数
shared_ptr可以将unique_ptr移动过来。
shared_ptr的简单实现
#include <iostream>
#include <memory>
using namespace std;
template <typename T>
class MyShared_ptr {
public:
MyShared_ptr(T* ptr = nullptr) {
cout << __PRETTY_FUNCTION__ << endl;
_ptr = ptr;
_count = new int(1);
_weakcount = new int(0);
}
MyShared_ptr(const MyShared_ptr& sharedPtr) {
cout << __PRETTY_FUNCTION__ << endl;
_count = sharedPtr._count;
_ptr = sharedPtr._ptr;
++*_count;
_weakcount = sharedPtr._weakcount;
}
MyShared_ptr& operator=(const MyShared_ptr& sharedPtr) {
if (this == &sharedPtr) { //处理自赋值的情况
return *this;
}
--*_count;
if (*_count == 0) {
release();
}
_count = sharedPtr._count;
_ptr = sharedPtr._ptr;
++*_count;
_weakcount = sharedPtr._weakcount;
return *this;
}
T* get() {
return _ptr;
}
int use_count() {
return *_count;
}
T& operator*() {
return *_ptr;
}
T* operator->() {
return _ptr;
}
~MyShared_ptr() {
cout << __PRETTY_FUNCTION__ << endl;
--*_count;
cout << "_count: " << *_count << endl;
if (*_count == 0) {
release();
}
}
void release() {
delete _count;
delete _ptr;
delete _weakcount
_count = nullptr;
_ptr = nullptr;
_weakcount = nullptr;
}
//注意,在STL的正规实现中,引用计数和弱引用计数在一个结构体中,shared_ptr保存一个指向该结构体的指针,这里这么写是为了描述简单
int *_count; //引用计数
int *_weakcount; //弱引用计数
T *_ptr; //智能指针维护的控制块
};
在上述的实现中,基本描述了智能指针的组成模块,包括引用计数、弱引用计数、资源的维护。当然在标准STL中的shared_ptr,包含有许多额外的内容,这里就不全部实现了。
shared_ptr指针的循环引用及解决方法 —— weak_ptr
shared_ptr指针的循环引用问题指,两个或多个shared_ptr互相引用,导致shared_ptr的引用计数无法到0,而出现内存泄露的情况。
weak_ptr就是被设计解决该问题的工具。
weak_ptr的简易实现
首先下面给出简易的weak_ptr实现,用于和MyShared_ptr进行配合使用。
/* weak_ptr的典型实现为存储两个指针:
* 指向控制块的指针,即这里的_ptr;
* 指向来源shared_ptr的存储指针;
*/
template <typename T>
class MyWeak_ptr {
public:
T* _ptr;
MyShared_ptr<T> *_shared;
MyWeak_ptr() {
_ptr = nullptr;
_shared = nullptr;
}
MyWeak_ptr(MyShared_ptr<T>& shared): _ptr(shared.get()), _shared(&shared) {
cout << __PRETTY_FUNCTION__ << endl;
++*shared._weakcount; //只增加shared_ptr对象中的弱引用计数
}
MyWeak_ptr<T>& operator=(const MyShared_ptr<T>& shared) {
cout << __PRETTY_FUNCTION__ << endl;
_ptr = shared._ptr;
_shared = const_cast<MyShared_ptr<T> *>(&shared);
++*_shared->_weakcount; //只增加shared_ptr对象中的弱引用计数
return *this;
}
bool expired() {
if (_shared == nullptr)
return false;
return _shared->use_count() > 0;
}
MyShared_ptr<T> lock() {
if (_shared == nullptr) {
return MyShared_ptr<T>(nullptr);
}
return *_shared;
}
~MyWeak_ptr() {
cout << __PRETTY_FUNCTION__ << endl;
}
};
以上就是weak_ptr的简易实现,下面描述循环引用问题。
shared_ptr的循环引用问题
//引用循环问题
class Son1;
class Father1 {
public:
MyShared_ptr<Son1> _son;
Father1() {
cout << __PRETTY_FUNCTION__ << endl;
}
~Father1() {
cout << __PRETTY_FUNCTION__ << endl;
}
};
class Son1 {
public:
MyShared_ptr<Father1> _father; //注意:此处为shared_ptr
Son1() {
cout << __PRETTY_FUNCTION__ << endl;
}
~Son1() {
cout << __PRETTY_FUNCTION__ << endl;
}
};
int main()
{
Father1 *fa = new Father1();
Son1 *so = new Son1();
MyShared_ptr<Father1> father(fa);
MyShared_ptr<Son1> son(so);
father->_son = son;
son->_father = father;
cout << "father count: " << father.use_count() << endl;
cout << "son count: " << son.use_count() << endl;
return 0;
}
执行上述代码,可以获得一下输出:
$ ./test_shared_ptr.exe
MyShared_ptr<T>::MyShared_ptr(T*) [with T = Son1]
Father1::Father1()
MyShared_ptr<T>::MyShared_ptr(T*) [with T = Father1]
Son1::Son1()
MyShared_ptr<T>::MyShared_ptr(T*) [with T = Father1]
MyShared_ptr<T>::MyShared_ptr(T*) [with T = Son1]
father count: 2
son count: 2
MyShared_ptr<T>::~MyShared_ptr() [with T = Son1]
_count: 1 //程序结束后,Son1的引用还为1,无法被释放
MyShared_ptr<T>::~MyShared_ptr() [with T = Father1]
_count: 1 //程序结束后,Father1的引用还为1,无法被释放
可以看到,在程序结束之后,并没有调用Father1和Son1的析构函数,这就是由于Father1和Son1中的shared_ptr循环引用导致的内存泄露问题。
借助weak_ptr解决循环引用问题
接下来看如何使用weak_ptr来解决循环引用问题。
class Son2;
class Father2 {
public:
MyShared_ptr<Son2> _son;
Father2() {
cout << __PRETTY_FUNCTION__ << endl;
}
~Father2() {
cout << __PRETTY_FUNCTION__ << endl;
}
};
class Son2 {
public:
MyWeak_ptr<Father2> _father; //注意:此处为weak_ptr
Son2() {
cout << __PRETTY_FUNCTION__ << endl;
}
~Son2() {
cout << __PRETTY_FUNCTION__ << endl;
}
};
int main()
{
Father2 *fa = new Father2();
Son2 *so = new Son2();
MyShared_ptr<Father2> father(fa);
MyShared_ptr<Son2> son(so);
father->_son = son;
son->_father = father;
cout << "father count: " << father.use_count() << endl;
cout << "son count: " << son.use_count() << endl;
return 0;
}
注意,Son2中的Father为weak_ptr,执行上述代码,可得到一下打印:
$ ./test_shared_ptr.exe
MyShared_ptr<T>::MyShared_ptr(T*) [with T = Son2]
Father2::Father2()
Son2::Son2()
MyShared_ptr<T>::MyShared_ptr(T*) [with T = Father2]
MyShared_ptr<T>::MyShared_ptr(T*) [with T = Son2]
MyWeak_ptr<T>& MyWeak_ptr<T>::operator=(const MyShared_ptr<T>&) [with T = Father2]
father count: 1
son count: 2
MyShared_ptr<T>::~MyShared_ptr() [with T = Son2]
_count: 1
MyShared_ptr<T>::~MyShared_ptr() [with T = Father2]
_count: 0
Father2::~Father2() //释放Father2
MyShared_ptr<T>::~MyShared_ptr() [with T = Son2]
_count: 0
Son2::~Son2() //释放Son2
MyWeak_ptr<T>::~MyWeak_ptr() [with T = Father2]
可以看到,在程序执行结束后,分别执行了Father2和Son2的析构函数,这里的Father2和Son2被正确释放了,解决了之前的循环引用导致内存泄漏的问题。