目录
一、RAII
RAII:是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在
对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做
法有两大好处:
不需要显式地释放资源。
采用这种方式,对象所需的资源在其生命期内始终保持有效。
二、auto_ptr
C++98版本的库中就提供了auto_ptr的智能指针,
auto_ptr的实现原理:管理权转移的思想
下面简化模拟实现了一份 auto_ptr 来了解它的原理:
cpp
namespace SmartPtr
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr):ptr_(ptr)
{}
auto_ptr(auto_ptr<T>& ap) :ptr_(ap.ptr_)
{
ap.ptr_ = nullptr;// 所有权转移
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
// 检测是否为自己给自己赋值
if (this != &ap)
{
// 释放当前对象中资源
if (ptr_)
delete ptr_;
// 转移ap中资源到当前对象中
ptr_ = ap.ptr_;
ap.ptr_ = nullptr;
}
return *this;
}
~auto_ptr()
{
if (ptr_)
{
std::cout << "delete:" << ptr_ << std::endl;
delete ptr_;
}
}
// 像指针一样使用
T& operator*()
{
return *ptr_;
}
T* operator->()
{
return ptr_;
}
private:
T* ptr_;
};
};
并且可以使用以下代码进行测试:
cpp
int main()
{
using namespace std;
// 测试基本功能
int* p = new int(10);
SmartPtr::auto_ptr<int> sp1(p);
cout << "*sp1 = " << *sp1 << endl; // 测试 operator*
// 测试所有权转移
SmartPtr::auto_ptr<int> sp2(sp1);
if (sp1.operator->() == nullptr)
cout << "sp1 ownership transferred to sp2" << endl;
cout << "*sp2 = " << *sp2 << endl;
// 测试赋值操作符
int* p2 = new int(20);
SmartPtr::auto_ptr<int> sp3(p2);
sp2 = sp3;
if (sp3.operator->() == nullptr)
cout << "sp3 ownership transferred to sp2" << endl;
cout << "*sp2 after assignment = " << *sp2 << endl;
return 0;
}
学习 auto_ptr 只是为了更了解智能指针的历史与后面的智能指针的改进, auto_ptr 基本算一个被时代遗弃的产物,所以尽量不要去使用 auto_ptr 管理内存资源。
三、unique_ptr
unique_ptr 是对 auto_ptr 的改进,它不再有所有权转移,它旨在独占所有权, 即它只能有一个所有者。
下面来看一下简易版的 unique_ptr :
cpp
template<class T>
class unique_ptr
{
public:
unique_ptr(T *ptr):ptr_(ptr)
{}
~unique_ptr()
{
std::cout << "delete unique_ptr" << std::endl;
delete ptr_;
}
T* operator->()
{
return ptr_;
}
T& operator*()
{
return *ptr_;
}
private:
unique_ptr(unique_ptr<T> &up) = delete;
unique_ptr<T> operator=(unique_ptr<T >& up) = delete;
private:
T* ptr_;
};
简单粗暴的将拷贝构造和赋值运算符重载给 delete ,保证了所有权的唯一性。
但是,没了拷贝构造或赋值重载,这就意味着 unique_ptr 就没办法 "赋值" 给其他 unique_ptr 吗?事实并非如此, unique_ptr 内部还有一个移动构造与移动赋值函数,简化如下:
cpp
// 移动构造函数
unique_ptr(unique_ptr<T>&& up): ptr_(up.ptr_)
{
up.ptr_ = nullptr; // 转移所有权
}
// 移动赋值操作符
unique_ptr<T>& operator=(unique_ptr<T>&& up)
{
if (this != &up)
{
delete ptr_; // 删除当前持有的对象
ptr_ = up.ptr_; // 接收新对象的所有权
up.ptr_ = nullptr;
}
return *this;
}
我们仍可以使用 unique_ptr<int> p2(std::move(p1)) 类似的操作来进行所有权的转移。
学习到了这里,你有没有想过,为什么 auto_ptr 一定要使用所有权转移的概念,为什么 unique_ptr 一定要独享所有权,不允许多个指针指向同一个对象?
以下是可能发生的危害:
假设
unique_ptr
支持拷贝构造或赋值重载,会带来以下几个问题:
双重释放问题 :当多个
unique_ptr
指向同一个对象时,当其中一个unique_ptr
被销毁,它会释放所指向的对象;而其他指向相同对象的unique_ptr
也会在各自销毁时再次尝试释放同一对象,导致程序崩溃。资源泄漏问题 :如果
unique_ptr
被拷贝而没有转移所有权,多个指针都会管理同一个对象,当某个unique_ptr
释放对象后,其他unique_ptr
依然会尝试访问已被销毁的资源,导致悬空指针问题。
四、shared_ptr
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
在 shared_ptr 中使用引用计数来管理资源,那么就需要使得指向同一份资源的指针可以访问同一块区域以调整引用计数,所以这里的计数器不能是简单的类成员,我们可以将它设置为指针,在不同 shared_ptr 使用时将指针赋值给它们。
同时,因为库中的 shared_ptr 使用互斥锁保证线程安全,我们这里也可以设置一个锁为成员变量,可以使用库中的 lock::guard ,类似于智能指针(RAII),可以自动解锁,在析构时也不需要单独释放锁:
cpp
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr):ptr_(ptr), pmtx_(new std::mutex), pCount_(new int(1))
{}
shared_ptr(const shared_ptr<T>& sp):ptr_(sp.ptr_), pmtx_(sp.pmtx_), pCount_(sp.pCount_)
{
AddRef();
}
~shared_ptr()
{
Release();
}
private:
void AddRef()
{
std::lock_guard<std::mutex> lock(*pmtx_); // 自动管理锁的释放
(*pCount_)++;
}
void Release()
{
std::lock_guard<std::mutex> lock(*pmtx_);
if (--(*pCount_) == 0)
{
std::cout << "delete" << std::endl;
delete ptr_;
delete pCount_;
delete pmtx_;
}
}
private:
T* ptr_;
std::mutex *pmtx_;
int* pCount_;
};
但是, shared_ptr 还有一点不妥,当两个或以上的 shared_ptr 相互指向时,引用计数不会减为0,所以 C++11 还引入了 弱指针 weak_ptr 的概念,使用 weak_ptr 指向 shared_ptr 管理的对象不会增加 shared_ptr 的引用计数。
shared_ptr 可以隐式类型转换为 weak_ptr ,weak_ptr 可以通过它的成员函数 lock() 转化为 weak_ptr ,示例:
cpp
std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp = sp;// shared_ptr 可以直接隐式类型转化为 weak_ptr
if (std::shared_ptr<int> locked_sp = wp.lock())// weak_ptr通过使用lock转化为shared_ptr
{
// 成功获取 shared_ptr,安全使用对象
std::cout << *locked_sp << std::endl;
}
else
{
// 对象已经被销毁
std::cout << "The object is no longer available." << std::endl;
}