编写智能指针实现对象托管(二)

    xiaoxiao2021-12-14  18

    四、侵入式的智能指针

      虽然侵入式智能指针会将部分数据记录到对象本身,但它可以实现一些非侵入式智能指针无法实现的功能,比如从this指针构造一个合法的智能指针对象。参照标准库的shared_ptr和weak_ptr,我设计出这样一个智能指针机制:   同标准库的智能指针机制相比,该机制在对象基类中添加了计数指针和清除标记,将对象指针放到了计数对象中。该机制同样可以实现shared_ptr、weak_ptr的功能,而且,由于对象本身记录有计数指针,也可以从对象本身获取一个智能指针对象。其要求就是对象必须从一个基类派生,而且多继承时,还需要虚派生以保证基类部分的唯一性。   如果目标对象没有清除标记,那么这个智能指针的使用方法就跟标准库的非常类似。但是,我们要设计的对象必须是可以托管的,当对象交给后台托管后,强引用计数将变为0,而这时候对象却不能被释放。这就需要对“被托管”和“未被托管”的对象进行区分。一种可行的方案是使用一个特定的函数模板来创建托管对象,从这个函数模板创建的对象都打上托管标记,并且加入后台托管集合中,那么当强引用计数变为0时就能区别对待了。考虑到同步问题,其中的托管标记需要放在计数对象上。

    五、具体实现

      具体实现中包含:计数类、基类、强引用智能指针模板、弱引用智能指针模板、对象管理类。声明如下: struct ReferenceCount;//计数类 struct ObjectBase;//基类 template<class T>struct sharedptr;//强引用智能指针模板 template<class T>struct weakptr;//弱引用智能指针模板 struct ObjectManager;//对象管理类

    1.计数类

      计数类中至少要包含对象指针、弱引用计数和强引用计数三者,而且为了区分托管与非托管对象计数类中还需要一个成员,咱这里就用一个deleter来区分。具体代码如下: #include<atomic> struct ReferenceCount{//计数类 ObjectBase* ptr_obj;//对象指针 std::atomic<unsigned> cnt_strong;//强引用计数 std::atomic<unsigned> cnt_weak;//弱引用计数 void(*deleter)(ObjectBase*&);//强引用计数减至0时对象释放方法 };

    2.基类

      基类中包含清除标记和一些基本方法。为了让其它类型从该类型派生,虚析构方法是必须的。然后就是获取托管智能指针成员的方法,也需要允许派生类重写。具体代码如下: struct ObjectBase { unsigned use_count();//获取强引用计数 ObjectBase();//默认构造函数 private: ReferenceCount* ptr_cnt;//计数对象指针 unsigned t_cnt;//需要扫描成员数量 unsigned char t_ver;//扫描版本号 bool t_added;//是否已加入托管集合 void setcnt(ReferenceCount*p);//设置计数指针 protected: virtual ~ObjectBase() ;//析构方法:不允许直接delete virtual unsigned GetScanObjects(weakptr **pout);//用以获取需要扫描的智能指针 template friend struct weakptr;//友元 template friend struct sharedptr;//友元 friend struct ObjectManager;//友元 };

    3.weakptr模板

      weakptr代码结构如下: template struct weakptr{ weakptr();//默认构造空引用 weakptr(weakptr const&p);//复制构造增加弱引用计数 weakptr& operator=(const weakptr&w);//赋值语句 ~weakptr();//析构 weakptr& operator=(const sharedptr &w);//从sharedptr构造 friend bool operator==(weakptr const&w1, weakptr const&w2); friend bool operator!=(weakptr const&w1, weakptr const&w2);//比较 sharedptr lock();//获取一个sharedptr private: ReferenceCount* ptr_cnt;//指向计数对象 };   weakptr<T>主要提供赋值和比较操作,对于T并没有特殊要求,不论T是不是从ObjectBase派生。当weakptr<T>类型的对象指向一个托管的对象时,为了防止后台扫描线程扫描对象成员过程中,成员发生变化,必须在扫描过程中锁住赋值操作。因此weakptr<T>在T为可以托管的类型时,每次赋值都要经过锁。

    4.sharedptr模板

      sharedptr代码结构如下: template<class T>struct sharedptr { sharedptr();//默认构造空引用 sharedptr(sharedptr const&p);//复制构造增加弱引用计数和强引用计数 explicit sharedptr(T*p);//直接从对象指针构造 void addref();//增加引用计数 void release();//减少引用计数,并在强引用计数减为0时使用对象释放方法 sharedptr& operator=(const sharedptr&w);//赋值语句 friend bool operator==(sharedptr const&w1, sharedptr const&w2);//比较 friend bool operator!=(sharedptr const&w1, sharedptr const&w2); friend bool operator==(sharedptr const&w1, std::nullptr_t); friend bool operator!=(sharedptr const&w1, std::nullptr_t); ~sharedptr() { release(); }//析构 T& operator*()const; T* get()const; T*operator->()const; unsigned use_count()const; private: static T* castpointer(ObjectBase*p, ObjectBase*);//当第二个参数为ObjectBase派生类对象指针时使用 static T* castpointer(ObjectBase*p, ...); void construct(T*,ObjectBase*p);//当第二个参数为ObjectBase派生类对象指针时使用 void construct(T*p,...); static void deleter(ObjectBase*&p); ReferenceCount* ptr_cnt;//指向计数对象 friend struct weakptr<T>; friend struct ObjectManager; };   sharedptr<T>中,T为int 跟T为ObjectBase派生类时会在构造计数对象、析构对象、指针转换时有所不同,因此使用两个函数重载来区别对待.sharedptr的赋值不需要经过锁,因为只要是存在sharedptr指向的可以托管的对象,都会在后台根集合中有记录。处理引用计数也是一个难题,因为对象完全托管时,其强引用计数将变为0,从weakptr使用lock构造sharedptr时,不能单纯地判断ReferenceCount中的cnt_strong是否为0来决定结果是不是空的sharedptr,而是要通过判断ptr_cnt是否是nullptr来决定。这里咱通过通过认定cnt_strong为1时处于对象释放处理阶段来进行多线程同步:当cnt_strong 减至1时,其它所有尝试增加强引用计数的线程都进入等待,等该线程将cnt_strong设置为0时,其它尝试增加强引用计数的线程再根据ptr_obj进行操作。这使得cnt_strong 总是比实际值多1.

    5.ObjectManager类

      ObjectManager类中主要处理托管对象的扫描和释放,结构和成员如下: struct ObjectManager { //根节点 static ObjectManager& root(){ static ObjectManager r; return r; } //新增托管对象 template<class T, class ...Args>static sharedptr<T>newobj(Args...a); //强引用计数为0时的删除方法:不做任何处理 static void DeleteMethod(ObjectBase*&) {} //构造函数 ObjectManager(); //析构函数,释放所有管理对象 ~ObjectManager(); //进入操作 void EnterOperating(); //退出操作 void ExitOperating(); //添加对象到对象表 void AddObject(ObjectBase*p); //添加对象到栈列表并返回栈引用 sharedptr<ObjectBase> AddRootObject(ObjectBase*p); //移除所有对象 void ReleaseAll(); //立即发起一次扫描 void ScanNow(); //扫描线程 void ScanThread(); //扫描函数 void Scan(std::vector<ObjectBase*>& robjs, unsigned d = 0); //递归更新版本号 void UpdateVersion(unsigned char newversion, ObjectBase* p); //获取对象数 size_t GetObjectCount()const { return objs.size(); } //获取托管根数量 size_t GetRootCount()const { return rootobjs.size(); } //对象表,每个对象在其中至多只有一个记录 std::vector<ObjectBase*>objs; std::mutex mtobjs; //根节点表,每个对象在其中最多只有一个记录 std::vector<ObjectBase*>rootobjs; std::mutex mtstackobjs; //扫描次数统计 size_t scancnt; //未扫描成员数 size_t unscanMembers; //未扫描对象数量 size_t unscanCount; //当前版本号 unsigned char version; //状态变量,用以跟后台扫描线程同步 size_t state; //扫描释放毫秒秒间隔(大概) size_t scanTime; //间隔计数 size_t scanidx; //已进入的添加或删除节点的操作数量 signed enterOperatingCount; //进入锁 std::mutex mtEnter; //已退出的添加或删除节点的操作数量 std::atomic<signed> exitOperatingCount; };   注意到其中的newobj函数即为创建托管对象的专用函数,该函数需要对新建的对象做标记:计算对象托管成员数量、记录对象到根节点集合、记录对象到对象集合。扫描原理上一部分讲过,认为所有通过newobj函数生成的对象都在根节点记录,然后先扫描根节点集合,再扫描对象集合来找出可释放对象。为防止循环引用导致无限扫描,扫描过程需要先判断该对象是否被扫描过(版本号是否是最新)。扫描过程虽然是递归的,但是并不能使用递归函数,因为对象数量一多就很容易爆栈。最后就是要处理好从托管智能指针weakptr<T>变量lock得到sharedptr的问题,这个sharedptr要根据对象是否托管来决定是否加入根节点集合。

    六、具体代码和测试结果

      先看VS2015的测试结果,可以看到上一部分会导致内存耗尽的代码,在使用对象托管之后,进程内存占用呈锯齿状,已经可以由后台自动释放了。    测试程序代码: #include<iostream> #include<atomic> #include<thread> #include<mutex> #include<vector> #include<Windows.h> struct ReferenceCount;//计数类 struct ObjectBase;//基类 template<class T>struct sharedptr;//强引用智能指针模板 template<class T>struct weakptr;//弱引用智能指针模板 struct ObjectManager;//对象管理类 struct ReferenceCount {//计数类 ObjectBase* ptr_obj;//对象指针 std::atomic<unsigned> cnt_strong;//强引用计数 std::atomic<unsigned> cnt_weak;//弱引用计数 void(*deleter)(ObjectBase*&);//强引用计数减至0时对象释放方法 }; struct ObjectBase { unsigned use_count() { if (ptr_cnt) { unsigned cnt = ptr_cnt->cnt_strong; if (cnt > 1)return cnt - 1; }return 0; }//获取强引用计数 ObjectBase() :ptr_cnt(nullptr), t_cnt(0), t_ver(0), t_added(false) {}//默认构造函数 private: ReferenceCount* ptr_cnt;//计数对象指针 unsigned t_cnt;//需要扫描成员数量 unsigned char t_ver;//扫描版本号 bool t_added;//是否已加入托管集合 void setcnt(ReferenceCount*p) { if (ptr_cnt != p) { if (ptr_cnt&&--ptr_cnt->cnt_weak == 0) delete ptr_cnt; ptr_cnt = p; if (ptr_cnt) ++ptr_cnt->cnt_weak; } } protected: virtual ~ObjectBase() { setcnt(nullptr); }//析构方法:不允许手动delete virtual unsigned GetScanObjects(weakptr<ObjectBase>**pout) {//用以获取需要扫描的智能指针 //默认实现认为:1.类型从ObjectBase或ObjectBase的单继承派生类 单继承派生 2.成员只有sharedptr或者weakptr模板类对象 *pout = reinterpret_cast<weakptr<ObjectBase>*>(const_cast<ObjectBase*>(this + 1)); return t_cnt; } template<class T>friend struct weakptr;//友元 template<class T>friend struct sharedptr;//友元 friend struct ObjectManager;//友元 }; template<class T>struct weakptr { weakptr() :ptr_cnt(nullptr) {}//默认构造空引用 weakptr(weakptr const&p) :ptr_cnt(p.ptr_cnt) { if (ptr_cnt)++ptr_cnt->cnt_weak; }//复制构造增加弱引用计数 weakptr& operator=(const weakptr&w); ~weakptr() {//析构 if (ptr_cnt&&--ptr_cnt->cnt_weak == 0) delete ptr_cnt; } weakptr& operator=(const sharedptr<T>&w) {//因为sharedptr和weakptr结构一样,这里可以简单粗暴地调用operator=(weakptr) return *this = reinterpret_cast<weakptr const&>(w); } friend bool operator==(weakptr const&w1, weakptr const&w2) { return w1.ptr_cnt == w2.ptr_cnt; } friend bool operator!=(weakptr const&w1, weakptr const&w2) { return w1.ptr_cnt != w2.ptr_cnt; } sharedptr<T> lock();//该操作需要放到后面实现 private: ReferenceCount* ptr_cnt;//指向计数对象 }; template<class T>struct sharedptr { sharedptr() :ptr_cnt(nullptr) {}//默认构造空引用 sharedptr(sharedptr const&p) :ptr_cnt(p.ptr_cnt) { addref(); }//复制构造增加弱引用计数和强引用计数 explicit sharedptr(T*p) { construct(p, p); } void addref() { if (ptr_cnt) { ++ptr_cnt->cnt_strong; ++ptr_cnt->cnt_weak; } } void release() { if (ptr_cnt) { if (--ptr_cnt->cnt_strong == 1) { if (ptr_cnt->deleter) { ptr_cnt->deleter(ptr_cnt->ptr_obj); ptr_cnt->cnt_strong = 0; } else { ObjectBase* p = ptr_cnt->ptr_obj; ptr_cnt->ptr_obj = nullptr; ptr_cnt->cnt_strong = 0; delete p; } } if (--ptr_cnt->cnt_weak == 0) { delete ptr_cnt; ptr_cnt = 0; } } } sharedptr& operator=(const sharedptr&w) { if (ptr_cnt != w.ptr_cnt) {//两者所引用的计数对象不同时才需要赋值 release(); ptr_cnt = w.ptr_cnt; addref(); } return *this; } friend bool operator==(sharedptr const&w1, sharedptr const&w2) { return w1.ptr_cnt == w2.ptr_cnt; } friend bool operator!=(sharedptr const&w1, sharedptr const&w2) { return w1.ptr_cnt != w2.ptr_cnt; } friend bool operator==(sharedptr const&w1, std::nullptr_t) { return w1.use_count() == 0; } friend bool operator!=(sharedptr const&w1, std::nullptr_t) { return w1.use_count() != 0; } ~sharedptr() { release(); }//析构 T& operator*()const { T *p = get(); if (p)return *p; throw std::runtime_error("空引用"); } T* get()const { return castpointer(ptr_cnt ? ptr_cnt->ptr_obj : nullptr, (T*)nullptr); } T*operator->()const { T *p = get(); if (p)return p; throw std::runtime_error("空引用"); } unsigned use_count()const { return ptr_cnt ? (unsigned)ptr_cnt->cnt_strong - 1 : 0; } private: static T* castpointer(ObjectBase*p, ObjectBase*) { return dynamic_cast<T*>(p); } static T* castpointer(ObjectBase*p, ...) { return reinterpret_cast<T*>(p); } void construct(T*, ObjectBase*p) { if (p->ptr_cnt) { ptr_cnt = p->ptr_cnt; ++ptr_cnt->cnt_weak; unsigned val = ptr_cnt->cnt_strong; while (val == 1) val = ptr_cnt->cnt_strong; while (!ptr_cnt->cnt_strong.compare_exchange_weak(val, val == 0 ? 2 : (val + 1))) { while (val == 1) val = ptr_cnt->cnt_strong; } } else { ptr_cnt = new ReferenceCount(); ptr_cnt->cnt_weak = 1; ptr_cnt->cnt_strong = 2; p->setcnt(ptr_cnt); ptr_cnt->ptr_obj = p; ptr_cnt->deleter = nullptr; } } void construct(T*p, ...) { ptr_cnt = new ReferenceCount(); ptr_cnt->ptr_obj = reinterpret_cast<ObjectBase*>(p); ptr_cnt->deleter = &sharedptr::deleter; ptr_cnt->cnt_weak = 1; ptr_cnt->cnt_strong = 2; } static void deleter(ObjectBase*&p) { delete reinterpret_cast<T*>(p); p = nullptr; } ReferenceCount* ptr_cnt;//指向计数对象 friend struct weakptr<T>; friend struct ObjectManager; }; struct ObjectManager { //根节点 static ObjectManager& root() { static ObjectManager r; return r; } //新增托管对象 template<class T, class ...Args>static sharedptr<T>newobj(Args...a) { T* p = new T(a...); sharedptr<T> sp(p); sp.ptr_cnt->deleter = &ObjectManager::DeleteMethod; p->t_cnt = (sizeof(T) - sizeof(ObjectBase)) / sizeof(weakptr<ObjectBase>); ObjectManager::root().AddRootObject(p); ObjectManager::root().AddObject(p); return sp; } //删除方法 static void DeleteMethod(ObjectBase*&) {} //构造函数 ObjectManager() :exitOperatingCount(0), enterOperatingCount(0), scanidx(0), scanTime(10), state(1), version(0), scancnt(0), unscanMembers(0) { std::thread(&ObjectManager::ScanThread, this).detach();//启动扫描线程 } //析构函数 ~ObjectManager() { state &= ~1; while (state & 2)Sleep(1); ReleaseAll(); } //进入操作 void EnterOperating() { mtEnter.lock(); enterOperatingCount++; mtEnter.unlock(); } //退出操作 void ExitOperating() { exitOperatingCount++; } //添加对象到对象表 void AddObject(ObjectBase*p) { if (p) { EnterOperating(); mtobjs.lock(); objs.push_back(p); mtobjs.unlock(); ExitOperating(); } } //添加对象到栈列表并返回栈引用 sharedptr<ObjectBase> AddRootObject(ObjectBase*p) { if (p) { if (unscanMembers > 1024 * 1024 * 8) {//当未扫描托管成员数量过多时,立即发起扫描 ScanNow(); } EnterOperating(); if (p->t_added) { sharedptr<ObjectBase> t(p); ExitOperating(); return t; } p->t_added = true;//标记为已经加入列表 unscanCount++; unscanMembers += p->t_cnt; mtstackobjs.lock(); rootobjs.push_back(p); mtstackobjs.unlock(); sharedptr<ObjectBase> pt(p); ExitOperating(); return pt; } return sharedptr<ObjectBase>(); } //移除所有对象 void ReleaseAll() { mtEnter.lock(); while (enterOperatingCount != exitOperatingCount)Sleep(0); std::vector<ObjectBase*> robjs; std::vector<ObjectBase*> wobjs; std::swap(robjs, objs); std::swap(wobjs, rootobjs); mtEnter.unlock(); for (size_t i = 0, j = robjs.size(); i != j; ++i) delete robjs[i]; wobjs.resize(0); } //立即发起一次扫描 void ScanNow() { unscanMembers = 0; unscanCount = 0; scanidx = 0; std::vector<ObjectBase*>robjs; mtEnter.lock(); while (enterOperatingCount != exitOperatingCount)Sleep(0); Scan(robjs); for (size_t i = 0, j = robjs.size(); i != j; ++i)delete robjs[i]; mtEnter.unlock(); } //扫描线程 void ScanThread() {//扫描线程 state |= 2; try { std::vector<ObjectBase*> robjs; size_t i, j; while (state & 1) { if (scanidx++ < scanTime) { Sleep(1000); continue; } scanidx = 0; //扫描 mtEnter.lock(); while (enterOperatingCount != exitOperatingCount)Sleep(0); Scan(robjs); if (rootobjs.max_size() - rootobjs.size()>rootobjs.size() / 4) rootobjs.shrink_to_fit(); if (objs.max_size() - objs.size()>objs.size() / 4) objs.shrink_to_fit(); mtEnter.unlock(); for (i = 0, j = robjs.size(); i != j; ++i) delete robjs[i]; robjs.resize(0); robjs.shrink_to_fit(); } } catch (...) {} state &= ~2; } //扫描函数 void Scan(std::vector<ObjectBase*>& robjs, unsigned d = 0) { unscanMembers = 0; unscanCount = 0; size_t i, j; //更新版本号 version++; //扫描栈对象表 ObjectBase**proot = rootobjs.data(); for (i = 0, j = rootobjs.size(); i != j; ++i) { if (proot[i]->use_count() == 0) { std::swap(rootobjs[i--], rootobjs[--j]); proot[j]->t_added = false;//标记为未加入列表 } else UpdateVersion(version, proot[i]); } rootobjs.resize(j); //扫描对象表并记录要释放的对象以及 for (i = 0, j = objs.size(); i != j; ++i) { if (objs.data()[i]->t_ver != version) { robjs.push_back(objs.data()[i]); objs.data()[i--] = objs.data()[--j]; } } objs.resize(j); } //递归更新版本号 void UpdateVersion(unsigned char newversion, ObjectBase* p) { const int idx_mov = (sizeof(weakptr<ObjectBase>*) + sizeof(ObjectBase*)) / sizeof(unsigned); std::vector<unsigned>stack(8192); int current = 0; (ObjectBase*&)stack[0] = p; START:; if (p) { if (p->t_ver == newversion) { goto SWITCH;//返回上一步 }//该对象已更新过或者对象成员非托管 p->t_cnt = p->GetScanObjects((weakptr<ObjectBase>**)(&stack[current + sizeof(ObjectBase*) / sizeof(unsigned)])); p->t_ver = newversion; for (stack[current + idx_mov] = 0; stack[current + idx_mov] <p->t_cnt; stack[current + idx_mov] ++) { ReferenceCount**pcnts = (ReferenceCount**)(stack[current + idx_mov] + (weakptr<ObjectBase>*&)stack[current + sizeof(ObjectBase*) / sizeof(unsigned)]); if (*pcnts == nullptr || (*pcnts)->deleter != &ObjectManager::DeleteMethod)continue; if ((current += idx_mov + 1) + idx_mov >= stack.size()) stack.resize(current + idx_mov + 1); p = dynamic_cast<ObjectBase*>((*pcnts)->ptr_obj); (ObjectBase*&)stack[current] = p; //stack[current-1] = 1;//返回位置标1 goto START; Next:; } } SWITCH:; if (current == 0)return; current -= idx_mov + 1; p = (ObjectBase*&)stack[current]; goto Next; } //获取对象数 size_t GetObjectCount()const { return objs.size(); } //获取托管根数量 size_t GetRootCount()const { return rootobjs.size(); } //对象表,每个对象在其中至多只有一个记录 std::vector<ObjectBase*>objs; std::mutex mtobjs; //根节点表,每个对象在其中最多只有一个记录 std::vector<ObjectBase*>rootobjs; std::mutex mtstackobjs; //扫描次数统计 size_t scancnt; //未扫描成员数 size_t unscanMembers; //未扫描对象数量 size_t unscanCount; //当前版本号 unsigned char version; //状态,-1时退出线程 size_t state; //扫描释放毫秒秒间隔(大概) size_t scanTime; //间隔计数 size_t scanidx; //已进入的添加或删除节点的操作数量 signed enterOperatingCount; //进入锁 std::mutex mtEnter; //已退出的添加或删除节点的操作数量 std::atomic<signed> exitOperatingCount; }; template<class T> weakptr<T>& weakptr<T>::operator=(const weakptr<T>&w) {//后台扫描时要锁住赋值操作 if (ptr_cnt != w.ptr_cnt) {//两者所引用的计数对象不同时才需要赋值 ObjectManager::root().EnterOperating(); if (ptr_cnt&&--ptr_cnt->cnt_weak == 0) delete ptr_cnt; ptr_cnt = w.ptr_cnt; if (ptr_cnt) ++ptr_cnt->cnt_weak; ObjectManager::root().ExitOperating(); } return *this; } template<class T>sharedptr<T> weakptr<T>::lock() { if ((ptr_cnt&&ptr_cnt->deleter == &ObjectManager::DeleteMethod)) { sharedptr<ObjectBase> t = ObjectManager::root().AddRootObject(ptr_cnt->ptr_obj); return reinterpret_cast<sharedptr<T>&>(t); } else { sharedptr<T> t; if (ptr_cnt) { unsigned val = ptr_cnt->cnt_strong; while (val == 1) val = ptr_cnt->cnt_strong; if (ptr_cnt->ptr_obj == nullptr)return t; while (!ptr_cnt->cnt_strong.compare_exchange_weak(val, val == 0 ? 2 : (val + 1))) { while (val == 1) val = ptr_cnt->cnt_strong; if (ptr_cnt->ptr_obj == nullptr)return t; } t.ptr_cnt = ptr_cnt; } return t; } } struct Node:ObjectBase{ weakptr<Node> head; weakptr<Node> next; }; int main(){ for (int i = 1; i; ++i) { sharedptr<Node>root = ObjectManager::newobj<Node>(); root->next = ObjectManager::newobj<Node>(); root->next.lock()->head = root; } }   实际上,代码中还有许多问题没有解决,比如多个线程从同一个ObjectBase*进行sharedptr构造会导致异常,但这代码已基本实现了上一部分的想法。   另外咱建有个C++交流学习群(群号244953928),期待各位喜欢C++的伙伴加入O(∩_∩)O。
    转载请注明原文地址: https://ju.6miu.com/read-968276.html

    最新回复(0)