c++智能指针
提出的本意是内存泄漏的背景,不想手动去管理内存,交给一个类去管理,当类离开自身的作用域时会自动调用析构函数,释放已经申请的内存。
auto_ptr
所有的智能指针类均被定义在 memory 头文件中
以下是两个基本的初始化方式:
// 方式 1
std::auto_ptr<int> sp1(new int(1));
// 方式 2
std::auto_ptr<int> sp2;
sp2.reset(new int(1));
智能指针(smart pointer)sp1 和 sp2 均持有一个在堆上分配的 int 对象,值都是 1,这两块堆内存都在 sp1 和 sp2 释放时得到释放。
缺点(被废弃的原因):
当复制一个 auto_ptr 对象时(拷贝构造或者拷贝复制时),原 auto_ptr 对象所持有的堆内存对象也会被转移给复制出来的新的 auto_ptr 对象,例如:
std::auto_ptr<int> sp1(new int(1));
std::auto_ptr<int> sp2(sp1); // 此时利用拷贝构造,sp1 持有的堆对象被转移给 sp2 了
std::auto_ptr<int> sp3(new int(1));
std::auto_ptr<int> sp4;
sp4 = sp3; // 此时利用拷贝赋值,sp3 持有的堆对象被转移给 sp4 了
unique_ptr
为了弥补上述 auto_ptr 的缺点,提出 unique_ptr,这种 sp 对其持有的堆内存具有唯一的拥有权,也就是说该 sp 堆资源的引用计数永远是 1
三个基本的初始化方式
// 方式 1
std::unique_ptr<int> sp1(new int(2));
// 方式 2
std::unique_ptr<int> sp2;
sp2.reset(new int(2));
// 方式 3
std::unique_ptr<int> sp3 = std::make_unique<int>(2); // 推荐使用,更加的安全
当采用了以下 unique_ptr 核心源码后:
template <typename T, typename Deletor>
class unique_ptr {
// 其它代码省略
public:
// 移动构造函数
unique_ptr(unique_ptr& rhs) {
this->m_pT = rhs.m_pT;
// 源对象释放
rhs.m_pT = nullptr;
}
// 移动赋值
unique_ptr& operator=(unique_ptr& rhs) {
this->m_pT = rhs.m_pT;
// 源对象释放
rhs.m_pT = nullptr;
return *this;
}
// 拷贝构造函数和赋值运算符被标记为 delete
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
private:
T* m_pT;
};
再测试:
std::unique_ptr<int> sp1(std::make_unique<int>(3));
// 以下代码无法通过编译
std::unique_ptr<int> sp2(sp1); // 无法通过编译
std::unique_ptr<int> sp3;
sp3 = sp1; // 无法通过编译
// 以下可以通过编译且成功运行
std::unique_ptr<int> sp4(std::make_unique<int>(4));
std::unique_ptr<int> sp5(std::move(sp4));// 调用移动构造函数
std::unique_ptr<int> sp6;
sp6 = std::move(sp5);// 调用移动赋值函数
shared_ptr
与 unique_ptr 对其持有的资源具有独占性不同的是,shared_ptr 持有的资源可以在多个 shared_ptr 之间共享,每多一个 shared_ptr 对资源的引用,资源的引用计数就会增加 1,在每一个指向该资源的 shared_ptr 对象析构时,资源的引用计数都会减少 1,最后一个 shared_ptr 对象析构时,若发现资源引用计数为 0,则将释放其持有的资源。
三个基本的初始化方式
// 方式 1
std::shared_ptr<int> sp1(new int(5));
// 方式 2
std::shared_ptr<int> sp2;
sp2.reset(new int(5));
// 方式 3
std::shared_ptr<int> sp3;
sp3 = std::make_shared<int>(5); // 推荐使用
可以使用 use_count() 方法来获取当前管理的资源的引用计数,使用 reset() 方法来释放对当前管理的资源的引用。
以下为 shared_ptr 的一种实现:
/* 智能指针 sharedPtr 实现 */
template <typename T>
class sharedPtr {
public:
sharedPtr(T* ptr = nullptr) : _ptr(ptr), _refCount(new int(1)) {}
// 拷贝构造
sharedPtr(const sharedPtr& other) : _ptr(other._ptr), _refCount(other._refCount) {
(*_refCount)++;
}
// 拷贝赋值
sharedPtr<T>& operator=(const sharedPtr& other) {
// 防止自己给自己赋值
if (this != &other) {
// 原本只剩我一个指向的时候,释放原本的指向
if (--(*(this->_refCount)) == 0) {
delete this->_ptr;
delete this->_refCount;
}
_ptr = other._ptr;
_refCount = other._refCount;
*(_refCount)++;
}
return *this;
}
// 重载解引用
T& operator*() {
return *(this->_ptr);
}
// 重载箭头
T* operator->() {
return this->_ptr;
}
~sharedPtr() {
--(*this->_refCount);
if (this->_refCount == 0) {
delete _ptr;
delete _refCount;
_ptr = nullptr;
_refCount = nullptr;
}
}
private:
T* _ptr;
int* _refCount; /* 所有指向该对象的指针指向同一个计数器 */
};
weak_ptr
weak_ptr 是一个不控制资源资源生命周期的智能指针,是对对象的一种弱引用,只提供了对其管理的资源的一个访问手段,引入它的目的是协助 shared_ptr 工作。
weak_ptr 可以从一个 shared_ptr 或另一个 weak_ptr 对象构造,shared_ptr 可以直接赋值给 weak_ptr,也可以通过 weak_ptr 的 lock 函数来获得 shared_ptr。weak_ptr 的构造和析构不会引起引用计数的增加或减少,可用来解决 shared_ptr 相互引用时出现的死锁问题。
因为无论通过哪种方式来创建 weak_ptr,都不会增加资源的引用计数,所以当我们用 weak_ptr 来引用资源时,需要采用 expired 方法来检测引用的资源是否有效, 方法返回 true 说明其引用的资源已经失效,返回 false 说明该资源仍然有效。