C++智能指针
智能指针是一种用于管理动态分配内存的对象,可以在内存不再需要时自动释放。智能指针通过重载了指针操作符的类来实现,以模拟指针的行为,但具有自动资源管理的功能。
RAII思想
RAII
是资源获取即初始化(Resource Acquisition Is Initialization)的缩写,是一种 C++ 编程范式,它通过在对象的构造函数中获取资源,利用对象的生命周期来管理资源的释放,从而确保资源在不再需要时被正确释放。智能指针是RAII
思想的一种产物,在多线程中,守卫锁Guard Lock
也是一种常见的RAII
风格的加锁方式。
RAII
风格的Lock Guard(Linux下原生线程库)#pragma once #include <iostream> #include <pthread.h> //RAII枷鎖 class Mutex { public: Mutex(pthread_mutex_t* mutex) :_mutex(mutex) { pthread_mutex_init(_mutex,nullptr); } void lock() { pthread_mutex_lock(_mutex); } void unlock() { pthread_mutex_unlock(_mutex); } ~Mutex() { pthread_mutex_destroy(_mutex); } private: pthread_mutex_t* _mutex; }; class LockGuard { public: LockGuard(pthread_mutex_t* pmtx) :_mtx(pmtx) { _mtx.lock(); } ~LockGuard() { _mtx.unlock(); } private: Mutex _mtx; };
智能指针
C++标准库提供了两种主要的智能指针:std::unique_ptr
和 std::shared_ptr
。此外,C++17还引入了 std::weak_ptr
。
在C++98中还引入了auto_ptr
std::auto_ptr
C++在C++98中就引入了auto_ptr
,但是但在 C++11 标准中已经被废弃,并在 C++17 中被完全移除。这是因为 auto_ptr
存在一些严重的缺陷。
问题如下:
int main()
{
std::auto_ptr<int> p1(new int);
std::auto_ptr<int> p2(p1);
*p1 = 10;
*p2 = 10;
std::cout << *p1 << std::endl;
std::cout << *p2 << std::endl;
return 0;
}
上面的代码以指针的角度来看,就是让两个指针维护同一块地址空间,但是上面程序运行会奔溃。这是标准库中的auto_ptr
简单实现一份auto_ptr
然后了解一下auto_ptr
的问题。
#pragma once
#include <iostream>
namespace ding
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{
}
auto_ptr(auto_ptr<T> & sp)
:_ptr(sp._ptr)
{
sp._ptr = nullptr;
}
//赋值运算符(注意先释放原空间在赋值 以及自己给自己赋值的情况)
auto_ptr<T>& operator=(const auto_ptr<T>& sp)
{
if (&sp != this)
{
if (_ptr != nullptr)
{
delete _ptr;
}
_ptr = sp._ptr;
sp._ptr = nullptr;
}
return *this;
}
//模拟指针行为
T& operator*() const
{
return *_ptr;
}
T* operator->() const
{
return _ptr;
}
~auto_ptr()
{
if (_ptr != nullptr)
{
delete _ptr;
}
}
private:
T* _ptr;
};
}
使用auto_ptr
#include "auto_ptr.h"
int main()
{
ding::auto_ptr<int> p1(new int);
ding::auto_ptr<int> p2(p1);
*p1 = 10;
*p2 = 10;
std::cout << *p1 << std::endl;
std::cout << *p2 << std::endl;
return 0;
}
当执行完ding::auto_ptr<int> p2(p1);
后,p1对象的指针以及被置空了,在对齐解引用,就会出现对空指针解用的问题。空指针是不解引用的。这种情况编译器应该要出警告的,但是我的编译器还是能运行的,只是退出码不正常。这应该是vs2022的bug(我用的是2022测试版的)。
只能通过监视窗口来看
C++98提供的auto_ptr
最大的问题就是这个了,称之为管理权转移,将p1的管理权转移给p2然后自己悬空。导致了auto_ptr
直接被禁用,甚至在17中被移除了。
std::unique_ptr
经过十几年后,在C++11中,出现了比auto_ptr
更靠谱的unique_ptr
。unique_ptr
主要解决auto_ptr
带来的问题,在unique_ptr
中直接禁用了拷贝和赋值。
std::unique_ptr
的使用int main() { std::unique_ptr<int> p1(new int); std::unique_ptr<int> p2(p1);//error p2 = p1;//error }
unique_ptr
不能再使用拷贝和赋值了,所以他的使用场景就被限制了,对于有些地址空间需要更多的指针来维护是不能实现的。
std::unique_ptr简易模拟实现
unique_ptr
就很简单了,对比auto_ptr
直接把拷贝构造和赋值用C++11的语法用delete禁用就行了。
namespace ding
{
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{
}
unique_ptr(unique_ptr<T>& sp) = delete;
//赋值运算符(注意先释放原空间在赋值 以及自己给自己赋值的情况)
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
//模拟指针行为
T& operator*() const
{
return *_ptr;
}
T* operator->() const
{
return _ptr;
}
~unique_ptr()
{
if (_ptr != nullptr)
{
delete _ptr;
}
}
private:
T* _ptr;
};
}
std::shared_ptr
C++11还提供了更靠谱的智能指针shared_ptr
。解决了auto_ptr
的悬空问题和unique_ptr
的防拷贝问题。
std::shared_ptr
使用引用计数来跟踪有多少个智能指针指向相同的资源,当最后一个 std::shared_ptr
被销毁时,它所管理的资源也会被释放。
shared_ptr
在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。- 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减 一。
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
- 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对 象就成野指针了。
shared_ptr 简易模拟实现
namespace ding
{
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr )
:_ptr(ptr)
,_ref_count(new size_t(1))
{
}
shared_ptr(shared_ptr<T>& sp)
:_ptr(sp._ptr)
,_ref_count(sp._ref_count)
{
++(*_ref_count);
}
T& operator=(shared_ptr<T>& sp)
{
if (this != &sp)
{
//释放
if (--(*_ref_count) == 0)
{
delete _ref_count;
delete _ptr;
}
_ptr = sp._ptr;
_ref_count = sp._ref_count;
++(*_ref_count);
}
}
T& operator*() const
{
return *_ptr;
}
T* operator->()const
{
return _ptr;
}
~shared_ptr()
{
if (--(*_ref_count) == 0)
{
delete _ptr;
delete _ref_count;
}
}
private:
T* _ptr;
size_t* _ref_count;//引用计数
};
}