智能指针的原理和常用的智能指针及其实现
智能指针的原理
C++中的智能指针是一种封装了动态内存的管理并在对象生命周期终止时自动删除内存的指针。
智能指针通过重载指针操作符来实现指针的基本功能,并通过 RAII (Resource Acquisition Is Initialization) 的概念,确保资源在作用域结束时进行释放,从而防止内存泄漏。
常用的智能指针
shared_ptr
shared_ptr是一个共享所有权的智能指针,其所指向的对象可以被多个shared_ptr对象共享,每一个shared_ptr对象都保留一个引用计数器,记录有多少个shared_ptr在共同拥有该对象,当最后一个shared_ptr被销毁时,该对象才会被销毁。
shared_ptr一般用于需要共享和动态管理的情况。
实现原理
采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会减1,当计数为0的时候会自动的释放动态分配的资源。
- 智能指针将一个计数器与类指向的对象相关联,引用计数器跟踪共有多少个类对象共享同一指针
- 每次创建类的新对象时,初始化指针并将引用计数置为1
- 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数
- 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数
- 调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)
unique_ptr
unique_ptr是一个独占所有权的智能指针,其所指向的对象只能有一个unique_ptr拥有,不能被其他指针或引用共享,当unique_ptr被销毁时,其所指向的对象也会被销毁。
unique_ptr一般用于需要确保资源独占且无需共享的情况。
- 转移一个unique_ptr将会把所有权全部从源指针转移给目标指针,源指针被置空。
- unique_ptr不支持普通的拷贝和赋值操作,不能用在STL标准容器中 。
- 如果你拷贝一个unique_ptr,那么拷贝结束后,这两个unique_ptr都会指向相同的资源,造成在结束时对同一内存指针多次释放而导致程序崩溃。
weak_ptr
weak_ptr是一个弱引用的智能指针,它指向一个由shared_ptr管理的对象,但不会增加该对象的引用计数。当shared_ptr对象被销毁时,其所有关联的weak_ptr对象都会自动失效。
weak_ptr一般用于避免由shared_ptr导致的循环引用问题。
weak_ptr不保证它指向的内存一定是有效的,在使用之前使用函数lock()检查weak_ptr是否为空指针。
auto_ptr
auto_ptr是一个C++11之前的智能指针,其实现方式与unique_ptr类似,也是一种独占所有权的智能指针,能够自动释放内存资源。auto_ptr的特点在于可以安全地转移所有权,但是它也存在很多缺陷。
auto_ptr的缺陷包括以下几点:
- 不支持数组类型:auto_ptr只适用于单个动态分配的对象,不能用于动态分配的数组;
- 不支持多线程:auto_ptr的拷贝构造函数和赋值运算符并不安全,不能保证多线程下的正确性;
- 容易造成悬空指针:当多个auto_ptr指向同一块内存时,如果其中一个auto_ptr被销毁,那么其他auto_ptr所指向的内存将变成无效的悬空指针。
实现引用计数的智能指针
以下是一个实现了引用计数的智能指针(类似shared_ptr)。
template <typename T>
class SmartPtr
{
public:
SmartPtr(T* p = nullptr)
: ptr_(p), pCount_(nullptr)
{
if (ptr_)
{
pCount_ = new int(1);
}
}
SmartPtr(const SmartPtr& other)
: ptr_(other.ptr_), pCount_(other.pCount_)
{
if (ptr_)
{
++(*pCount_);
}
}
~SmartPtr()
{
release();
}
SmartPtr& operator=(const SmartPtr& other)
{
if (this != &other)
{
release();
ptr_ = other.ptr_;
pCount_ = other.pCount_;
if (ptr_)
{
++(*pCount_);
}
}
return *this;
}
T* operator->() const
{
return ptr_;
}
T& operator*() const
{
return *ptr_;
}
private:
void release()
{
if (ptr_ && --(*pCount_) == 0)
{
delete ptr_;
delete pCount_;
ptr_ = nullptr;
pCount_ = nullptr;
}
}
T* ptr_;
int* pCount_;
};