C++不像java,C#,Python这些自带垃圾回收机制的GC语言,C++需要程序员手动申请和释放堆区内存,这种可控性在一定程度上使得堆区内存释放并不会出现大幅度的性能下降。反观GC语言,会出现垃圾收集过程中导致性能下降,特别是内存使用的高峰期需要频繁进行收集时。垃圾收集的执行时机不确定,可能会影响程序的表现。但这种可控性要求程序员必须手动处理内存否则会出现内存泄漏。因此c++11引入了智能指针。智能指针是存储指向动态分配对象指针的类,用于生存期的控制,能够确保在离开指针所作用域时,自动销毁分配的对象,防止内存泄漏,智能指针的核心实现技术是引用技术。
分类
智能指针一共包含5种,下面依次介绍。
auto_ptr
auto_ptr的特点是会自动将智能指针内部的资源管理对象指针赋值为nullptr,因此使用起来很危险,所以使用较少。auto_ptr
是 C++98 引入的智能指针,但在 C++11 中被标记为 弃用,并在 C++17 中正式移除。应使用 std::unique_ptr
替代
int main()
{
auto_ptr<A>p(new A);
//此时p1 = p 然后p = nullptr
auto_ptr<A>p1(p);
//程序会崩溃,访问nullptr
p = 2;
return 0;
}
auto_ptr是被严格禁止使用的,并且不允许将auto_ptr和vector使用,原因就是auto_ptr每次进行拷贝之后就会将原始值制空,如下:
int main() {
vector<auto_ptr<A>> vec{auto_ptr<A> (new A()),auto_ptr<A> (new A()),auto_ptr<A> (new A())};
auto_ptr<A> p1 (vec[0]);
return 0;
}
当将vec[0]元素拷贝p1时,vec[0]就会被置为NULL,后续访问就会抛出异常,可见是非常危险的。
scoped_ptr
删除了拷贝构造函数和赋值运算符函数,智能独占资源。
unique_ptr
unique_ptr也删除了拷贝构造函数和赋值运算符函数。但是提供了移动构造函数和移动赋值函数,可以进行资源的转移,同时保持独占,因此使用很频繁。性能最接近裸指针,大小是一倍的裸指针大小,而shared_ptr大小是两倍裸指针不止,并且因为引用计数线程安全会涉及到锁等操作性能不如unique_ptr。
int main()
{
unique_ptr<A>p(new A);
//使用move进行移动构造
unique_ptr<A>p1 = move(p);
//reset方法可以让unique_ptr解除对原始内存的管理,也可以来初始化一个独占智能指针
p1.reset();
p1.reset(new A);
//如果要获取独占智能指针的原始地址,可以调用get()方法
cout << p1.get()->a << endl;
return 0;
}
weak_ptr
弱引用的智能指针,它不共享指针,不能操作资源,不能改变引用计数,是用来监视shared_ptr的,解决循环引用问题,多线程的安全。
int main()
{
//使用use_count来查看当前所观测资源的引用计数
shared_ptr<A>p(new A);
weak_ptr<A>p1(p);
cout << p1.use_count() << endl;
shared_ptr<A>p2(p);
p.reset();
cout << p1.use_count() << endl;
//使用expured来查看当前资源是否已经释放
if (p1.expired())
{
cout << "expired!" << endl;
}
else
{
cout << "not expired!" << endl;
}
//通过lock()可以获取 管理所监测的资源的shared_ptr对象
shared_ptr<A>p3(p1.lock());
cout<<p1.use_count() << endl;
//使用reset来清空监测对象,此时不再监测任何资源对象
p1.reset();
cout << p1.use_count() << endl;
return 0;
}
实现一个weak_ptr
template
class Weak_ptr
{
public:
Weak_ptr() {}
Weak_ptr(const Weak_ptr& o)
{
ptr = o.ptr;
res = o.res;
res->incwref();
}
Weak_ptr(const Shared_ptr&o)
{
res = o.res;
ptr = o.ptr;
res->incwref();
}
Weak_ptr(Weak_ptr&&o)
{
ptr = o.ptr;
res = o.res;
}
Weak_ptr(Shared_ptr&& o)
{
ptr = o.ptr;
res = o.res;
res->incwref();
res->descref();
}
Weak_ptr& operator=(const Weak_ptr& _Right) noexcept {
Weak_ptr(_Right).swap(*this);
return *this;
}
Weak_ptr& operator=(const Shared_ptr& o)
{
this->ptr = o.ptr;
this->res = o.res;
this->res->incwref();
return this; } void reset() noexcept { // release resource, convert to null weak_ptr object Weak_ptr{}.swap(this);
}
void swap(Weak_ptr& _Other) noexcept {
swap(this->ptr,_Other.ptr);
swap(this->res,_Other.res);
}
_NODISCARD bool expired()
{
return res&&res->use_count() == 0;
}
_NODISCARD long use_count() const noexcept {
return res ? res->use_count() : 0;
}
_NODISCARD Shared_ptr lock() const noexcept { // convert to shared_ptr
Shared_ptr _Ret;
if (this->res&&this->res->use_count())
{
_Ret.ptr = this->ptr;
_Ret.res = this->res;
this->res->incref();
}
return _Ret;
}
~Weak_ptr()
{
res->descwref();
}
private:
T* ptr{nullptr};
Res* res{ nullptr };
};
循环引用
weak_ptr的出现一方面是为了解决在多线程时通过lock方法检测原始资源是否被释放,保证多线程安全的,另一方面就是为了解决shared_ptr造成的循环引用问题。
#include<iostream>
#include<memory>
using namespace std;
class B;
class A
{
public:
shared_ptr<B>ptr;
A() { cout << "A构造" << endl; }
~A() { cout << "A析构" << endl; }
};
class B
{
public:
shared_ptr<A>ptr;
B() { cout << "B构造" << endl; }
~B() { cout << "B析构" << endl; }
};
int main()
{
shared_ptr<A>a (new A);
shared_ptr<B>b (new B);
a->ptr = b;
b->ptr = a;
return 0;
}
之所以会出现A和B都没释放就是因为类A和类B中分别有对方的智能指针成员变量,当b析构时发现引用计数还不为0则无法释放new B的空闲,导致a的引用计数也不为0,所以内存泄漏。
解决办法:使用weak_ptr,一般在定义对象的地方使用shared_ptr在引用的地方使用weak_ptr
#include<iostream>
#include<memory>
using namespace std;
class B;
class A
{
public:
weak_ptr<B>ptr;
A() { cout << "A构造" << endl; }
~A() { cout << "A析构" << endl; }
};
class B
{
public:
weak_ptr<A>ptr;
B() { cout << "B构造" << endl; }
~B() { cout << "B析构" << endl; }
};
int main()
{
shared_ptr<A>a (new A);
shared_ptr<B>b (new B);
a->ptr = b;
b->ptr = a;
return 0;
}
unique_ptr
unique_ptr是一个独占型的智能指针,不允许其他智能指针共享内部的指针,可以通过它的构造函数初始化一个独占智能指针,但是不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。
实现一个unique_ptr
template
class Unique_ptr
{
public:
Unique_ptr(Tptr) { this->ptr = ptr; } Unique_ptr(){} ~Unique_ptr() { delete ptr; } Unique_ptr(Unique_ptr&&o) { o.ptr = this->ptr; o.ptr = nullptr; } Unique_ptr(const Unique_ptr&) = delete; Unique_ptr& operator=(Unique_ptr&&o) { o.ptr = this->ptr; o.ptr = nullptr; } Unique_ptr& operator=(const Unique_ptr&) = delete; T get () const
{
return ptr;
}
T* operator->() const
{
return ptr;
}
T& operator*()
{
return *ptr;
}
void reset(Tnptr = nullptr) { T optr = ptr;
ptr = nptr;
delete optr;
}
T* release()
{
auto tmp = ptr;
ptr = nullptr;
return tmp;
}
private:
T* ptr{nullptr};
};
shared_ptr
共享智能指针,允许多个智能指针对象同时管理一块内存,共享智能指针是一个模板类,可以有三种方式进行初始化,构造函数初始化,std::make_shared辅助函数,reset方法。共享智能指针对象初始化完毕之后就指向了要管理的堆区内存。
实现一个简单的智能指针:
template<typename T>
class Res {
atomic<int> _Uses{ 0 };
atomic<int> _Weaks{ 0 };
public:
//构造函数 构造Res
Res(T* ptr) :_Uses(1), _Weaks(1) { this->ptr = ptr; }
//当有新的智能指针指向 引用计数++
inline void incref() { _Uses.fetch_add(1); }
inline void incwref() { _Weaks.fetch_add(1); }
//当 引用计数为0 代表资源需要释放先释放 资源管理类 中原始指针 指向的 堆区内存 然后释放 资源管理类
inline void descref()
{
if (_Uses.fetch_sub(1) == 1)
{
descwref();
delete ptr;
}
}
inline void descwref()
{
if (_Weaks.fetch_sub(1) == 1)
{
delete this;
}
}
//返回引用计数
size_t use_count() { return _Uses.load(); }
T* ptr;
};
template<typename T>
class Weak_ptr;
template<typename T>
class Shared_ptr {
//只要使用了模板类 那么不管什么地方 类的指针定义,类在堆区创建 都必须要用Res<T>的形式
Res<T>* res{nullptr};
T* ptr{nullptr};
template<typename T>
friend class Weak_ptr;
public:
Shared_ptr() {}
explicit Shared_ptr(const Shared_ptr& o)
{
res = o.res;
ptr = o.ptr;
if (res)
res->increase();
}
Shared_ptr(Shared_ptr&& o) { res = o.res;o.res = nullptr;ptr = o.ptr;o.ptr = nullptr; }
Shared_ptr(T* ptr)
{
if (ptr)
{
res = new Res<T>(ptr);
this->ptr = ptr;
}
}
size_t use_count() { return res ? res->use_count() : 0; }
Shared_ptr& operator=(const Shared_ptr& o)
{
if (&o == this)
{
return *this;
}
if (res) { res->desc(); }
res = o.res;
ptr = o.ptr;
if (res)res->increase();
return *this;
}
Shared_ptr& operator=(Shared_ptr&& o)
{
if (res) { res->desc(); }
res = o.res;
ptr = o.ptr;
o.ptr = nullptr;
o.res = nullptr;
return *this;
}
void reset()
{
if (res) { res->descref(); }
res = nullptr;
ptr = nullptr;
}
void reset(T* ptr)
{
if (res) { res->desc(); }
res = new Res<T>(ptr);
this->ptr = ptr;
}
T& operator*()
{
if (ptr)
{
return *get();
}
throw runtime_error("Dereferencing a null pointer!");
}
T* operator->()const {
if (ptr)
{
return get();
}
throw runtime_error("Dereferencing a null pointer!");
}
T* get() const {
if (ptr)
{
return ptr;
}
return nullptr;
}
~Shared_ptr()
{
if (res) { res->descref(); }
}
};
MSCV1929种STL的shared_ptr源码解读
首先来看一下整个的布局:
shared_ptr和weak_ptr对外提供API接口,二者继承自Ptr_base,Ptr_base实现了一系列的构造方法,并且包含了两个成员变量,原始指针(数据块)和资源管理类指针(控制块),Ref_count_base实现了_Uses引用计数和_Weaks引用计数,并且包含两个纯虚函数_Delete_this(释放控制块资源)和_Destroy(释放数据块资源).
控制块有三种类型:Ref_count、Ref_count_del、Ref_count_del_alloc,分别是支持单独的指针,指针+自定义删除器、指针+自定义删除器+分配器。三者从Ref_count_base派生,重写了纯虚函数。
控制块
首先是_Destory()和_Delete_this()这两个虚函数:
- _Destory()释放指针指向的资源;_Uses ==0时执行。
- _Delete_this()释放控制块自身的资源;_Weaks == 0时执行。
_Uses和_Weaks都是原子操作加减,确保引用计数的线程安全。智能指针只有引用计数是线程安全的,管理的原始指针的一系列操作并不是安全的。实现引用计数的安全主要是两种方式:
- 使用原子变量操作记录_Uses和_Weaks
- weak_ptr.lock()通过CAS操作来实现自旋锁
着重看一下weak.lock()通过CAS实现自旋锁:
这是weak_ptr的lock方法:
创建了智能指针对象并调用了Ptr_base中的_Construct_from_weak方法
可以看到这里检查了_Rep原始指针以及_Uses是否存在,具体看一下控制块中的_Incref_nz函数:
这里通过获取_Uses值并volatile修饰确保每次从内存中读取,然后使用Windows提供的API_InterlockedCompareExchange(当前值,新值,期望值)返回旧值,如果当前值和期望值相同,则当前值更新为新值,如果不相等说明其他线程进行了操作,此时修改_Count不断轮询进行尝试添加引用计数。
Ptr_base
Ptr_base主要是实现了一些construct函数,这些函数再shared_ptr和weak_ptr中调用,_Construct_from_weak就是其中一个函数。具体代码:
template <class Ty>
class Ptr_base {
public:
Ptr_base() = default;
~Ptr_base() = default;
using element_type = std::remove_extent_t<Ty>;
long use_count() const noexcept {
return _rep ? _rep->_Use_count() : 0 ;
}
element_type* get() const noexcept { return _ptr; }
protected:
template <class Ty2>
void _Move_construct_from(Ptr_base<Ty2>&& other) noexcept {
_ptr = other._ptr;
_rep = other._rep;
other._ptr = nullptr;
other._rep = nullptr;
}
template <class Ty2>
void _Copy_construct_from(const Ptr_base<Ty2>& other) noexcept {
if (other._rep) {
other._rep->Incref();
}
_ptr = other._ptr;
_rep = other._rep;
}
template <class Ty2>
void _Alias_construct_from(const Ptr_base<Ty2>& other, element_type* ptr) noexcept {
if (other._rep) {
other._rep->Incref();
}
_ptr = _ptr;
_rep = other._rep;
}
template <class Ty2>
void _Alias_move_construct_from(Ptr_base<Ty2>&& other, element_type* ptr) noexcept {
_ptr = _ptr;
_rep = other._rep;
other._ptr = nullptr;
other._rep = nullptr;
}
// weak->shared
template <class Ty2>
bool _Construct_from_weak(const Ptr_base<Ty2>& other) noexcept {
if (other._rep && other._rep->_Incref_nz()) { // 要确保_rep有效,且uses!=0
_ptr = other._ptr;
_rep = other._rep;
return true;
}
return false;
}
// shared->weak
template <class Ty2>
void _Weakly_construct_from(const Ptr_base<Ty2>& other) noexcept {
if (other._Rep) {
_ptr = other._ptr;
_rep = other._rep;
_rep->_Incwref();
}
}
void _Swap(Ptr_base& other){
using namespace std;
std::swap(_ptr, other._ptr);
std::swap(_rep, other._rep);
}
void _Decwref() noexcept {
if (_rep) {
_rep->_Decwref();
}
}
void _Decref() noexcept {
if (_rep) {
_rep->_Decref();
}
}
private:
element_type* _ptr = nullptr;
Ref_count_base* _rep = nullptr;
};
shared_ptr
在shared_ptr中可以看到很多enable_if_t这是c++17后增加用来支持数组,无需自定义删除器,通过is_array_v类型萃取实现的。并且可以看到_Set_ptr_rep_and_enable_shared中传入的new _Ref_count<_Ux>(_Owner._Ptr)这是创建了控制块,注意控制块不一定只能在堆区创建,也可以是栈区,取决于我们提供的自定义分配器。
_Set_ptr_rep_and_enable_shared函数,就是根据构造函数传递的原始指针,控制块(包括引用计数,删除器,自定义分配器)去构建完善的体系。并且会通过_Can_enable_shared来判断是否构造函数传经来的指针是一个指向enable_shared_from_this的类,如果是会出实现enable_shared_from_this类中的_Wptr指针。
enable_shared_from_this
源码:
template <class _Ty>
class enable_shared_from_this { // provide member functions that create shared_ptr to this
public:
using _Esft_type = enable_shared_from_this;
_NODISCARD shared_ptr<_Ty> shared_from_this() {
return shared_ptr<_Ty>(_Wptr);
}
_NODISCARD shared_ptr<const _Ty> shared_from_this() const {
return shared_ptr<const _Ty>(_Wptr);
}
_NODISCARD weak_ptr<_Ty> weak_from_this() noexcept {
return _Wptr;
}
_NODISCARD weak_ptr<const _Ty> weak_from_this() const noexcept {
return _Wptr;
}
protected:
constexpr enable_shared_from_this() noexcept : _Wptr() {}
enable_shared_from_this(const enable_shared_from_this&) noexcept : _Wptr() {
// construct (must value-initialize _Wptr)
}
enable_shared_from_this& operator=(const enable_shared_from_this&) noexcept { // assign (must not change _Wptr)
return *this;
}
~enable_shared_from_this() = default;
private:
template <class _Yty>
friend class shared_ptr;
mutable weak_ptr<_Ty> _Wptr;
};
可以看到确实在enable_shared_from_this构造函数张没有初始化_Wptr,而是在shared_ptr的构造函数中通过类型萃取进而初始化_Wptr来支持通过this创建shared_ptr,举个例子:
错误案例:
#include<iostream>
#include<memory>
using namespace std;
class A
{
public:
~A() {}
shared_ptr<A> test()
{
return shared_ptr<A>(this);
}
};
int main()
{
shared_ptr<A>p (new A);
p->test();
return 0;
}
运行程序崩溃,因为使用new A创建一个p来管理堆区内存,当我们调用test方法会再次创建一个shared_ptr注意这两个shared_ptr是各自独立的,并不是共享一个控制块,因此当test执行完毕之后new A的堆区资源就已经被释放了,而p作为局部变量被析构时又会释放一次堆区资源,因此程序崩溃。
正确解决:
#include<iostream>
#include<memory>
using namespace std;
class A:public enable_shared_from_this<A>
{
public:
~A() { cout << "析构" << endl; }
shared_ptr<A> test()
{
return shared_from_this();
}
};
int main()
{
shared_ptr<A>p = make_shared<A>();
shared_ptr<A>p1 = p->test();
return 0;
}
make_shared
make_shared重新定义了一个控制块Ref_count_obj2包含数据块,这样只需要一次分配即可。
这样可以减少内存碎片,提升程序运行效率。并且一次性分配内存资源可以有效避免内存泄漏的可能性。不会因为先申请数据块资源却因为后申请控制块失败而导致的数据库资源内存泄漏问题。
缺点:1.查看Ref_count_obj2可以看到,无法自定义删除器。
2.资源延迟释放问题,正常来讲只要_Uses为0即可释放数据块资源,当_Weaks为0释放控制块资源,但是因为make_shared将两者内存申请在一起无法分开释放资源,因此数据块资源的释放会收到控制块的影响,会延迟释放资源。