文章目录
一、使用智能指针的原因
某些情况下,申请了空间,new或者malloc了,但是可能没有释放该空间。
例如,在异常捕获中,先调用了某个函数,这个函数中途发生了错误且申请了一块空间,虽然我们捕获了异常,但没有将这个函数申请的空间释放。
try {
Func();
} catch (exception& e) {
...
}
例如,这里的 Func() 函数申请了一块地址空间后报错了,我们虽然捕获了该异常,却没有释放 Func() 申请的空间。由此发生内存泄漏。在长期不关闭的服务器上,内存泄漏是很严重的问题。
二、智能指针的设计思想
简而言之就是,设计一个模板类,将原本的指针传给这个类,在析构函数中将它自动释放,因为出了一个类的作用域,会自动调用析构函数。通过这个类对象生命周期来控制资源的释放。即,把管理一份资源的责任托管给了一个对象。
另外,还重载了运算符 * 和 -> 。
template<class T>
class smart_ptr {
public:
smart_ptr(T* ptr = nullptr) // 构造函数中赋值
: _ptr(ptr) {}
~smart_ptr() {// 析构函数中自动释放
if(_ptr)
delete _ptr;
T& operator*() {
return *_ptr;
}
T* operator->() {
return _ptr;
}
}
private:
T* _ptr;
};
smart_ptr<int> sp(new int); // 使用该智能指针类
三、一种不再使用的智能指针:auto_ptr
当出现拷贝构造时,由于是浅拷贝,会使得两个智能指针指向同一块地址空间,生命周期结束时,还会调用两次析构函数,相当于释放了两次同一块地址空间。
因此,曾经的智能指针 auto_ptr 使用了一种方法:管理权转移。
auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr) {
sp._ptr = nullptr; // 将原本的指针置空
}
由于原本的释放,会判断是否为空,如果为空就不释放。所以这里不会产生释放两次的情况。
但又有了新的问题,原本的智能指针不能再使用了(已经变为空指针了)。
四、C++11中的智能指针一:unique_ptr
头文件:
#include <memory>
unique_ptr 的原理是,不允许拷贝:
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
详细版本是:
template<class T>
class unique_ptr {
public:
unique_ptr(T* ptr = nullptr) // 构造函数中赋值
: _ptr(ptr) {}
~unique_ptr() {// 析构函数中自动释放
if(_ptr) delete _ptr;
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
}
private:
T* _ptr;
};
unique_ptr<int> sp(new int); // 使用该智能指针类
五、C++11中的智能指针二:shared_ptr
5.1 原理
shared_ptr 的原理是,使用引用计数来判断,是否该释放。
当引用计数不为 0 时,说明除了自己还有其他对象在使用该份资源,不需要释放,只需要将引用计数 -1;当引用计数为 0 时,说明自己是最后一个使用该资源的对象,才真正的释放。
具体方法:第一个对象,在堆上申请一块空间,来维护该对象的引用计数(使用指针的方式来维护,方便后面浅拷贝时,能通过指针改变引用计数的值):
private:
T* _ptr;
int* _pRefCount; // 维护该对象的引用计数
在构造函数中,初始化为 1 :
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pRefCount(new int(1)) {}
后面每一个拷贝对象,都将原本的引用计数加 1 :
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pRefCount(sp._pRefCount) {
++(*_pRefCount);
}
析构时,将引用计数减 1 ,当引用计数为 0 时,释放该资源:
~shared_ptr() {
if (--(*_pRefCount) == 0 && _ptr) {
delete _ptr;
delete _pRefCount;
}
}
5.2 赋值操作
在赋值操作时会产生问题。
shared_ptr<int> sp1(new int);
shared_ptr<int> sp2(new int);
sp1 = sp2;
在上面这种情况下,应将 sp1 的引用计数 -1(若变0,还需释放),将 sp2 的引用计数 +1 。如下:
shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
// 如果不是自己赋值给自己
if (_ptr != sp._ptr) { //同一资源的对象赋值操作,也可以判断出来
if (--(*_pRefCount) == 0 && _ptr) { // 需要释放原资源的情况
delete _ptr;
delete _pRefCount;
}
// 赋值
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
++(*_pRefCount); // 已经是新的资源的引用计数了
}
return *this;
}
5.3 线程安全
但是目前,shared_ptr 不是线程安全的。如果两个线程同时使得引用计数 +1 或 -1,可能会产生问题。在 std 的 shared_ptr 中,还对此问题进行了解决。
5.4 使用
头文件:
#include <memory>
构造:
std::shared_ptr<int> p1; //不传入任何实参
std::shared_ptr<int> p2(nullptr); //传入空指针 nullptr
std::shared_ptr<int> p3(new int(10));
std::shared_ptr<int> p3 = std::make_shared<int>(10);
//调用拷贝构造函数
std::shared_ptr<int> p4(p3);//或者 std::shared_ptr<int> p4 = p3;
//调用移动构造函数
std::shared_ptr<int> p5(std::move(p4)); //或者 std::shared_ptr<int> p5 = std::move(p4);
注意,同一普通指针不能同时为多个 shared_ptr 对象赋值,否则会导致程序发生异常。例如:
int* ptr = new int;
std::shared_ptr<int> p1(ptr);
std::shared_ptr<int> p2(ptr);//错误