C++ shared_ptr&&weak_ptr的简单介绍和仿写
Posted Jqivin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ shared_ptr&&weak_ptr的简单介绍和仿写相关的知识,希望对你有一定的参考价值。
文章目录
shared_ptr
一、shared_ptr的简单介绍
- shared_ptr是一个引用计数的智能指针,允许多个指针指向同一个对象。用引用计数来管理对象的资源。当用一个shared_ptr指针去初始化另一个shared_ptr指针的时候,引用计数会加一。当某一个shared_ptr生命周期结束,引用计数会减一。当引用计数为0 的时候,shared_ptr所指向的资源会被释放,引用计数器也会被释放。
- 结构
二、shared_ptr的使用
1.函数介绍
①unique()函数
:用来判断shared_ptr是否是资源的唯一拥有者。
②use_count()函数
:检查引用计数。
③get()函数
:得到shared_ptr所指向的堆区资源。
④reset()函数
:重置shared_ptr.参数为一个原生指针
⑤swap()函数
:交换两个shared_ptr。
主要函数如下图:
2.使用
shared_ptr的构造函数是不能进行隐式转换的。使用了explicit关键字。
shared_ptr<vector<int>> vc = new vector<int>({1,2,3}); //编译错误
shared_ptr<vector<int>> vc (new vector<int>({1,2,3})); //正确
class Object
{
private:
int value;
public:
Object(int val = 0) :value(val) { cout << "construct Object" << endl; }
~Object() { cout << "destruct Object" << endl; }
int Value() { return value; }
const int Value() const { return value; }
};
int main()
{
shared_ptr<Object> obj(new Object(10));
cout << obj->Value() << (*obj).Value() << endl; //10 10
shared_ptr<Object> obj2(new Object(20));
cout << obj2->Value() << endl; //20
obj2 = obj;
cout << obj2->Value() << endl; //10
shared_ptr<Object> obj3(obj2);
cout << obj3->Value() << endl; //10
cout << obj.use_count() << endl; //3
obj3.reset(new Object(30));
cout << obj3->Value() << " obj3.use_count():" << obj3.use_count() << " obj.use_count:" << obj.use_count() << endl;
// 30 1 2
obj3.swap(obj2);
cout << obj2->Value() << " obj2.use_count():" << obj2.use_count() << " obj3.use_count:" << obj3.use_count() << endl;
// 30 1 2
return 0;
}
结果:
三、shared_ptr对象创建方法的讨论
1. 有两种常见的创建的方法:
(1)调用shared_ptr的构造函数创建
(2)调用make_shared函数创建。这个函数也是定义在memory头文件中。
shared_ptr<Object> obj1(new Object(10));
shared_ptr<Object> obj2 = make_shared<Object>(100);
shared_ptr<string> s = make_shared<string>("aaaa");
关于构造函数的创建就不必多说了,shared_ptr是非侵入式的,当传入的指针不为空时,在构造函数中会执行在堆区创建一块空间的操作,这块空间就是引用计数的空间,所以引用计数的空间和shared_ptr指向的资源并不在一块。所以这就发生了两次内存的分配。但是内存的分配和回收是C++最慢的单次操作了
。
而make_shared函数的创建规则是:合并这两块空间为一块,即可以同时为计数器和原生内存分配空间。一次就把他们创建完成,并且在一块释放。即把二者的内存视为一个整体进行管理
。
2. 有关make_shared函数
1. make_shared的优点:
(1)减少了单次分配内存的次数
(2)增大cache的局部性:计数器课原生内存紧邻,所以这就减少了一般的cachemiss。
(3)异常的安全性比另一种方案更优。
看下面的代码:如果采用构造函数创建的方法。这个dosomething函数的参数的构建顺序是,先new Object{1024}这个空间,然后看第二个参数是否抛出异常,如果抛出异常就执行return 0。所以这就导致构建的对象得不到释放。(因为智能指针还没有构建,只是开辟了原生内存)。但是如果使用make_shared函数来进行,就比较好了。开辟这两块空间是一起进行的,如果抛出异常就都不创建。
2. make_shared 的缺点:
可能会存在对象析构了,但空间没有释放的问题。
如下图所示,当在链表中使用智能指针的时候,因为开辟的是连续的空间,只有mcnt_s == 0 && mcnt_w == 0&& mptr == nullptr才能释放这一大块空间,但是这种情况下,Object对象已经析构掉了(执行了objlist.pop_front()),但是wp还在生存期,所以造成了这一大块空间要一直存在,直到weak_ptr的对象wp生存期结束之后,mcnt_w的值为0,才会释放这一大块空间。
weak_ptr
一、weak_ptr的简单介绍
weak_ptr是配合shared_ptr使用的一个智能指针。它指向shared_ptr管理的对象,但是不影响对象的生命周期,不会改变shared_ptr的引用计数。
无论是否有weak_ptr指向,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。
二、weak_ptr的使用
1.成员函数介绍
(1)lock()函数
:获得一个shared_ptr。
(2)expired()
:判断所指对象是否已经被销毁。
(3)reset()
:重置
(4)use_count()
:当前的引用计数值。
2. 使用
最常用的两个方法是lock()
和expired()
;
class Object
{
private:
int value;
public:
Object(int val = 0) :value(val) { cout << "construct Object" << endl; }
~Object() { cout << "destruct Object" << endl; }
int Value() { return value; }
const int Value() const { return value; }
};
int main()
{
shared_ptr<Object> objs1(new Object(10));
shared_ptr<Object> objs2(new Object(20));
weak_ptr<Object> objw(objs1);
cout << objw.use_count() << endl; //1
shared_ptr<Object> objs3= objw.lock(); //lock()
cout << objs1.use_count() << endl; //2
objs1.reset();
if (objw.expired())
cout << "1过期" << endl;
objs3.reset();
if (objw.expired())
cout << "2过期" << endl;
return 0;
}
结果:
首先,进入主函数之后,构建了两个对象,然后objw指向objs1,之后用objs3.lock() (用objs1构建的一个智能指针)来构建objs3,所以,objs1的引用计数加一变成2,然后objs1重置为空,引用计数减一,这时候objw指向的对象还没有被析构,所以不会打印“1过期”,当重置objs3位空时,引用计数变为0,对象被析构(打印destruct Object),所以objw指向的对象被析构,所以打印“2过期”。最后打印的“destrcut Object“是objs2生命周期结束的时候调用析构函数,判断引用计数为1,减一之后为0,把对象析构掉。
关于shared_ptr造成的循环引用以及解决
1.循环引用
代码如下:
#include<iostream>
using namespace std;
class child;
class parent
{
public:
shared_ptr<child> c;
parent(){ cout << "parent construct" << endl; }
void fun() { cout << "parent" << endl; }
~parent() { cout << "~parent" << endl; }
};
class child
{
public:
shared_ptr<parent> p;
child() { cout << "child construct" << endl; }
void fun() { cout << "chile" << endl; }
~child() { cout << "~child" << endl; }
};
int main()
{
shared_ptr<parent> par(new parent());
shared_ptr<child> pch(new child());
par->c = pch;
pch->p = par;
par->fun(); pch->fun();
return 0;
}
结果
:
由上面的结果可以看到,只调用了构造函数,对象一直都没有析构。这就造成了内存的泄露。这是因为Parent和Child对象内部,具有各自指向对方的 shared_ptr,加上 parent和child这两个shared_ptr,说明每个对象的引用计数都是2。当程序退出时,即使parent和child被销毁,也仅仅是导致引用计数变为了1,因此并未销毁Parent和Child对象。
2.解决方案
吧parent和child类里的智能指针换成weak_ptr可以解决。weak_ptr不会增加shared_ptr的引用计数。所以当执行下面两行代码时不会使得引用计数增加,所以,程序结束的时候,par和pch调用各自的析构函数,使得引用计数减一之后变为0,这时候就可以析构各自指向的对象了。
系统只管理栈区的对象,当函数执行结束的时候,系统自动调用par和pch的析构函数。堆区空间系统不管理,要程序员自己析构。
par->c = pch;
pch->p = par;
修改代码:
weak_ptr<child> c;
weak_ptr<parent> p;
修改之后的执行结果如下:
shared_ptr和weak_ptr的仿写
1. 代码展示:
namespace jqw
{
template<class T>
class RefCnt
{
private:
T* mPtr;
std::atomic<int> mCnt_s;
std::atomic<int> mCnt_w;
public:
RefCnt(T* ptr = nullptr) :mPtr(ptr)
{
if (mPtr != nullptr)
{
mCnt_s = 1;
mCnt_w = 0;
}
}
~RefCnt() {}
int getRef_s() { return mCnt_s; }
void addRef_s() { ++mCnt_s; }
int delRef_s() { return --mCnt_s; }
int getRef_w() { return mCnt_w; }
void addRef_w() { ++mCnt_w; }
int delRef_w() { return --mCnt_w; }
};
template <class T> class weak_ptr;
//删除器
template <class T>
struct MyDeletor
{
public:
void operator()(T* ptr) const
{
delete ptr;
}
};
//Deletor默认为MyDeletor<T>
template<class T, typename Deletor = MyDeletor<T> >
class shared_ptr
{
private:
T* mPtr;
RefCnt<T>* mpRefCnt;
Deletor myDeletor;
public:
shared_ptr(T* ptr = nullptr) :mPtr(ptr), mpRefCnt(nullptr)
{
if (mPtr != nullptr)
{
mpRefCnt = new RefCnt<T>(mPtr);
}
}
~shared_ptr()
{
if (mPtr != nullptr && 0 == mpRefCnt->delRef_s() )
{
myDeletor(mPtr);
if (mpRefCnt->getRef_w() == 0)
{
delete mpRefCnt; //
}
}
mPtr = nullptr;
mpRefCnt = nullptr;
}
T& operator*()const { return *mPtr; }
T* operator->() { return mPtr; }
//拷贝构造,本身还没有被构建对象
shared_ptr(const shared_ptr<T>& src) :mPtr(src.mPtr), mpRefCnt(src.mpRefCnt)
{
//传入的智能指针的自愿如果是空的话,不会new RefCnt对象
if (mPtr != nullptr)
{
mpRefCnt->addRef_s();
}
}
shared_ptr& operator=(const shared_ptr<T>& src)
{
if (&src == this) return *this;
if (mPtr != nullptr && 0 == mpRefCnt->delRef_s())
{
myDeletor(mPtr);
mPtr = nullptr;
myDeletor(mPtr);
if (mpRefCnt->getRef_w() == 0) //这个要进行判断,不然使用weak_ptr的expired函数会出现问题
{
delete mpRefCnt; //
}
mpRefCnt = nullptr;
}
mPtr = src.mPtr;
mpRefCnt = src.mpRefCnt;
//必须要判断,因为传入的智能指针的自愿如果是空的话,不会new RefCnt对象
if (mPtr != nullptr)
{
mpRefCnt->addRef_s();
}
return *this;
}
shared_ptr(shared_ptr<T>&& src)
{
mPtr = src.mPtr;
mpRefCnt = src.mpRefCnt;
src.mPtr = nullptr;
src.mpRefCnt = nullptr;
}
shared_ptr<T>& operator=(shared_ptr<T>&& src)
{
if (mPtr != nullptr && 0 == mpRefCnt->delRef_s())
{
myDeletor(mPtr);
mPtr = nullptr;
if (mpRefCnt->getRef_w() == 0)
{
delete mpRefCnt; //
}
mpRefCnt = nullptr;
}
mPtr = src.mPtr;
mpRefCnt = src.mpRefCnt;
}
// int use_count() { return mpRefCnt->getRef_s(); } error
int use_count()
{
if (mPtr != nullptr)
{
return mpRefCnt->getRef_s();
}
return 0;
}
operator bool() const
{
return (mPtr != nullptr);
}
void reset(T* p = nullptr)
{
if (mPtr != nullptr && 0 == mpRefCnt->delRef_s())
{
myDeletor(mPtr);
mPtr = nullptr;
if (0 == mpRefCnt->getRef_w())
{
delete mpRefCnt;
}
mpRefCnt = nullptr;
}
mPtr = p;
mpRefCnt = nullptr; //一定要写
if (mPtr != nullptr)
{
mpRefCnt = new RefCnt(mPtr);
}
}
void swap(shared_ptr<T>& src)
{
std::swap(mPtr, src.mPtr);
std::swap(mpRefCnt, src.mpRefCnt);
}
shared_ptr(const weak_ptr<T>& _w)
{
mPtr = _w.mPtr;
mpRefCnt = _w.mpRefCnt;
mpRefCnt->addRef_s();
}
friend class weak_ptr<T>;
};
template <class T>
class weak_ptr
{
private:
T* mPtr;
RefCnt<T>* mpRefCnt;
void release()
{
if (mpRefCnt != nullptr)
以上是关于C++ shared_ptr&&weak_ptr的简单介绍和仿写的主要内容,如果未能解决你的问题,请参考以下文章
C++笔记-auto_ptr&unique_ptr&shared_ptr&shared_ptr基本用法
C++笔记-auto_ptr&unique_ptr&shared_ptr&shared_ptr基本用法
深入了解C++ (15) | 源码分析auto_ptr & unique_ptr 设计