四、侵入式的智能指针
虽然侵入式智能指针会将部分数据记录到对象本身,但它可以实现一些非侵入式智能指针无法实现的功能,比如从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。