一个基于引用计数的智能指针类

axx1611 2006-12-13 10:14:53
自己刚写的,里面还混了一些调试代码,大概试了一下工作的还可以。
大家帮忙提点意见,顺便看看是否还有leak

template<typename T> class ref_obj;

template<typename T> class ref_ptr
{
public:
ref_ptr() : m_pobj(NULL) {}
ref_ptr(T *pdata);
ref_ptr(const ref_ptr<T> &rp);
~ref_ptr();
ref_ptr &operator = (const ref_ptr<T> &rp);
ref_ptr &operator = (T *pdata);
T &operator * () const;
T *operator -> () const;
private:
ref_obj<T> *m_pobj;
};

template<typename T> class ref_obj
{
public:
ref_obj(T *pdata) : m_pdata(pdata) { m_cref = 0L; }
virtual ~ref_obj() { delete m_pdata; }
T *get_data() const { return m_pdata; }
void inc_ref() const { if (m_pdata) m_cref++; std::cout << m_cref << std::endl; }
void dec_ref() const { if (m_cref) m_cref--; std::cout << m_cref << std::endl; if (!m_cref) delete this; }
private:
T *m_pdata;
mutable long m_cref;
};

template<typename T> ref_ptr<T>::ref_ptr(const ref_ptr<T> &rp)
: m_pobj(NULL) { operator = (rp); }

template<typename T> ref_ptr<T>::ref_ptr(T *pdata)
: m_pobj(new ref_obj<T>(pdata)) { m_pobj->inc_ref(); }

template<typename T> ref_ptr<T>::~ref_ptr()
{ if (m_pobj) m_pobj->dec_ref(); }

template<typename T> ref_ptr<T> &ref_ptr<T>::operator = (const ref_ptr<T> &rp)
{
if (m_pobj) m_pobj->dec_ref();
m_pobj = rp.m_pobj;
m_pobj->inc_ref();
return *this;
}

template<typename T> ref_ptr<T> &ref_ptr<T>::operator = (T *pdata)
{
if (m_pobj) m_pobj->dec_ref();
m_pobj = new ref_obj<T>(pdata);
m_pobj->inc_ref();
return *this;
}

template<typename T> T &ref_ptr<T>::operator * () const
{ return *m_pobj->get_data(); } // assert(m_pobj)

template<typename T> T *ref_ptr<T>::operator -> () const
{ return m_pobj->get_data(); }

////////////////////

测试代码:
#include <iostream>
#include <cstdlib>
#include <string>
#include "ref_ptr.h"

using namespace std;

void run();

class A
{
public:
A(const string &s) : m(s) { cout << "new A(" << m << ")" << endl; }
~A() { cout << "delete A(" << m << ")" << endl; }
const string &gets(void) const { return m; }
private:
string m;
};

int main()
{
run();
system("pause");
}

ref_ptr<A> f(ref_ptr<A> &s)
{
ref_ptr<A> sp = new A("string 1");
s = sp;
ref_ptr<A> q = new A("string 2");
sp = q;
q = sp;
return new A("string 3");
}

void run()
{
ref_ptr<A> pa;
ref_ptr<A> pb;
pb = f(pa);
cout << (*pa).gets().c_str() << endl;
cout << pb->gets().c_str() << endl;
}
...全文
1119 68 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
68 条回复
切换为时间正序
请发表友善的回复…
发表回复
artcpp 2007-04-03
  • 打赏
  • 举报
回复
mark
fprintf 2006-12-16
  • 打赏
  • 举报
回复
到30贴了- -上MJ...

发现v2的本质了。。只要分配空间的那个指针失效,就会释放空间,其他的指针都无权释放
基本上是出不了第一个指针的作用域的。
所以是错的。

结贴!
axx1611 2006-12-16
  • 打赏
  • 举报
回复
plainsong(短歌)() ( ) 信誉:205 Blog 2006-12-16 15:44:25 得分: 0



我并没有说用swap实现赋值完全没有代价,我只是说它的代价并不大,完全可以接收,而它带来的好处却很大。避免了拷贝构造和拷贝赋值不一致的问题,可以清晰地实现强异常保证。如果最终性能分析中发现这一逻辑成为了性能瓶颈,再来修改实现也不迟。不过我估计这种情况很难见到。

而且你这个智能指针还不够实用,真正实用的智能指针通常是要用线程安全的计数器,这样inc和dec都是高代价的,swap与它们相比完全可以乎略。


=========================================================================

这点上我的意见和你一致。


短歌如风 2006-12-16
  • 打赏
  • 举报
回复

我并没有说用swap实现赋值完全没有代价,我只是说它的代价并不大,完全可以接收,而它带来的好处却很大。避免了拷贝构造和拷贝赋值不一致的问题,可以清晰地实现强异常保证。如果最终性能分析中发现这一逻辑成为了性能瓶颈,再来修改实现也不迟。不过我估计这种情况很难见到。

而且你这个智能指针还不够实用,真正实用的智能指针通常是要用线程安全的计数器,这样inc和dec都是高代价的,swap与它们相比完全可以乎略。
axx1611 2006-12-16
  • 打赏
  • 举报
回复
v2将=()改进如下(没优化XD):
ref_ptr &operator = (const ref_ptr<T> &rp)
{
bool b = true;
if (rp.m_obj.m_pdata != m_obj.m_pdata)
{
b = false;
m_obj.dec_ref();
}
m_obj = rp.m_obj;
m_obj.inc_ref();
if (b) m_obj.dec_ref();
return *this;
}
可以解决上面
t = t
x = f(x)
的问题
不过t = s, s = t这种的还是不行
wojiushigege 2006-12-16
  • 打赏
  • 举报
回复
代码没有注释,看起来真累
axx1611 2006-12-16
  • 打赏
  • 举报
回复
对于swap方法,我们可以细化一下:

this_type & operator = ( this_type const & source )
{
this_type temp(source);
swap(temp);
return *this;
}

=>

temp.obj1 = src.obj1
inc obj1.ref
this.obj2 -> this.obj1
temp.obj1 -> temp.obj2
dec obj2.ref

而:

m_obj.dec_ref();
m_obj = rp.m_obj;
m_obj.inc_ref();
return *this;

=>

dec obj2.ref
this.obj2 = src.obj1
inc obj1.ref

哪个好点应该能看出来吧。

不过在调试期间用swap是比较好,比较稳定,不过release的时候还是直接写较好

axx1611 2006-12-16
  • 打赏
  • 举报
回复
你也没有明白我的意思~~

首先我说了v2已经不是基于引用计数了(但是使用了一部分的思想),v2尽管有问题,我仍然放出来的原因就是因为它可以解决循环引用,如果加以改进,可能会有一定的实用价值

循环引用固然可以交给设计者去避免 但是对于一个gc系统来说它是不完善的,存在着泄漏的危险
如果你非要使用者注意这注意那,那么我还是用回new delete好了

完全基于引用的智能指针的使用范围终归是有限的,这也是标准库不包含它的原因之一
短歌如风 2006-12-16
  • 打赏
  • 举报
回复
你还是没有明白我的意思。引用计数指针其实是维护“共享所有权”概念的一种智能指针,其共享的关键就是里面的计数器,你让每一个指针对象有一个独立的计数器,这样就已经失去了“共享”的含义。
对于以记录引用是来维护共享概念的智能指针来说,循环引用问题是很难消除的,无论是计数机制还是引用链机制;只能通过某些附加信息(比如维护前驱节点信息表然后用拓扑排序方法)去发现,然后解决,但这样就复杂化了。
循环引用问题也没有什么“无奈”的,只要分清所有权关系就可以了。A有B的一个智能指针,表明A拥有B的某个对象的一部分所有权。而对于链表来说,说前驱节点拥有后继节点是说不通的,所以就不应该使用智能指针而直接使用指针,所有节点的释放应该由“链表类”来负责。对于树来说,父拥有子可以理解,但子不能拥有父;另一种思路是父子平等,由“树”类来负责释放。
C++不是Java,没有垃圾回收;智能指针也不能解决所有问题,在C++中,有维护对象能力的智能指针外,还有容器,要结合起来使用。智能指针更适合于存储较高层次的“逻辑对象”,而在较底层的数据结构的实现,还是要向STL容器学习。
axx1611 2006-12-16
  • 打赏
  • 举报
回复
v2.0版是错误版本,每个引用一个独立计数,当第一个智能指针析构时被释放,其它的都无影响。因为只有第一个的计数是1,其它都大于1。只有所有同源的智针共享同一个引用计数才正确。

====================================================================

我知道v2已经不是严格意义上的引用计数了,我写它的时候也没打算那么写,是改代码的时候偶然发现的
不过也正因为它不是纯粹的引用计数,所以才能够突破循环引用。
尽管它有些问题(还很严重) 但是相对于引用计数对循环引用的无奈,这种方法还是有可能被改进的

短歌如风 2006-12-16
  • 打赏
  • 举报
回复
v2.0版是错误版本,每个引用一个独立计数,当第一个智能指针析构时被释放,其它的都无影响。因为只有第一个的计数是1,其它都大于1。只有所有同源的智针共享同一个引用计数才正确。

a->p = ref_ptr<stirng>(new string);
b->p = a.p;
c->p = a.p;

delete a将导致b->p和c->p无效,而b和c无影响。相当于a中的p是auto_ptr,而b和d中放的是普通指针:
a->p = auto_ptr<string>(new string);
b->p = a->p.get();
c->p = a->p.get();
短歌如风 2006-12-16
  • 打赏
  • 举报
回复
关于用swap实现拷贝赋值操作符其实是一种很常见的手段,可以大大简化拷贝赋值操作符的实现。

关于效率上,其实没什么影响,因为这个swap中并没有代价很大的拷贝操作通常是几条简单类型数据的交换过程,或者让我前面所说的是三个memcpy(如果对象比较大)。真正代价大的的拷贝构造一次,析构一次,这些操作也是多数拷贝构造操作符所必须的。而它的好处很明显,减少了重复代码,并且很简单地实现了强异常保证。而且它还可以用于其它赋值操作。而楼主的另一个赋值操作恰好需要改造以实现强异常保证:
m_obj.dec_ref();
m_obj = ref_obj(pdata););//如果这时发生异常,将导致m_pobj“无故地”计数多减一,最终将导致提前释放而产生内存非法操作
return *this;
如果用swap就没有问题了:
this_type temp(pdata);
temp.swap(*this);//no-throw
return *this;

楼主拷贝赋值是有问题的,如果是t=t这种代码,如果些时引用计数为1:
m_obj.dec_ref();//计数为0,释放*t
m_obj = rp.m_obj;//无效对象
m_obj.inc_ref();//无效对象
return *this;
应该先加后减。不过有一个简单方法,就是判断rp与this是否相等。

> ref_ptr<T> t(new T), s, r;
> s = t;
> r = s;
> t = r;
> 或者干脆t = t;
> 它不会像v1一样死锁(leak),而是会提前释放,经过上述语句后,T就被释放掉了XD。。

这种问题本来就不应该存在,如果有这种问题,说明代码有问题,很可能是拷贝赋值操作符的问题。
axx1611 2006-12-15
  • 打赏
  • 举报
回复
a_b_c_abc10(无论如何,我一定要去试一试,就算我不能证明我可以,) ( ) 信誉:100 Blog


template<typename T> class UPtr

===============================================================

这句语法错误,没有template<typename T>,
同样问题的还有UPtr<T> *m_pu; m_pu(new UPtr<T>(pdata))等。
阁下用的VC??

---------------------------------------------------------------


public:
T* m_obj;//因为只供本类访问,数据成员公有是可控的

===============================================================

good, 我写1.5也考虑过这个问题
当时认为编译器自动内联get_data()并不会影响效率,不过仔细想想既然代码可以做就不要交给编译器了。
不过对于计数值仍然不太赞同做成公有


不管是阁下的HasPtr还是我写的ref_ptr都有个问题就是空指针实际上都建立了一个UPtr或ref_obj对象,如下的代码:

ref_ptr<T> p = new T;
p = NULL; // 放弃对T的引用

这个简单的动作却要new一个ref_obj来存放NULL,然后又立即释放掉
本来的v1是可以用m_pobj=NULL表示空指针,但是它却要在引用对象时多做两个判断:

if (m_pobj) m_pobj->dec_ref();
...
if (m_pobj) m_pobj->inc_ref();

要么牺牲时间,要么牺牲空间,1.5选择了后者,不知道有没有更好的方法??
比如静态null指针对象??

另外阁下初始化UPtr计数为1似乎是个好主意。
改代码中。。。
a_b_c_abc10 2006-12-15
  • 打赏
  • 举报
回复
我也贴个:
template<typename T> class HasPtr
{
private:
template<typename T> class UPtr
{
public:
T* m_obj;//因为只供本类访问,数据成员公有是可控的
mutable unsigned int m_count;

UPtr(T* p):m_obj(p),m_count(1)//
{}
~UPtr(){delete m_obj;}
};

UPtr<T> *m_pu;
public:
HasPtr(T *pdata=NULL):m_pu(new UPtr<T>(pdata))
{}
~HasPtr()
{
--m_pu->m_count;
if (m_pu->m_count==0)
delete m_pu;
}
/////////////////////////
HasPtr(const HasPtr<T> &rp):m_pu(rp.m_pu)
{//复制构造不重新创建m_pu指向的数据对象,而是让它一起指向源对象中的数据对象
++m_pu->m_count;

assert(m_pu->m_count==rp.m_pu->m_count);//仅测试用
}

HasPtr &operator = (const HasPtr<T> &rp)
{
++rp.m_pu->m_count;
--m_pu->m_count ;//当前的m_pu要改变指向,因些要将先的计数-1
//如果是自赋值,前两句的效果是计数不变.

if(m_pu->m_count==0)
delete m_pu;

m_pu=rp.m_pu;

//assert(m_pu->m_count==rp.m_pu->m_count);仅测试用
return *this;
}
HasPtr &operator = (T *pdata)
{
--m_pu->m_count ;
if(m_pu->m_count==0)
delete m_pu;

m_pu=new UPtr<T>(pdata);
m_pu->m_count=1;
return *this;
}
T &operator * () const
{
return *(m_pu->m_obj);
}
T *const operator -> () const
{
return m_pu->m_obj;
}
};
axx1611 2006-12-15
  • 打赏
  • 举报
回复
pottichu(拉拉是头猪) ( ) 信誉:100 Blog 2006-12-15 11:03:27 得分: 0


说实在的,不喜欢楼主的代码风格,呵呵。
看你的代码是件头疼的事情.


===========================================================================

工程代码自然不敢这么写
不过这是贴在论坛上,个人觉得行数越少越好,省得拖鼠标。。。

---------------------------------------------------------------------------

aflyinghorse() ( ) 信誉:99 Blog 2006-12-15 11:36:07 得分: 0


ref_ptr(T *pdata = NULL)构造函数加上explicit?


===========================================================================

加了的话这样的初始化:

ref_ptr<T> p = new T;

就不行了吧。。。 我觉得这种写法更常用写,就没屏蔽

而且这个类既然重载了=,不explicit也应该是安全的。




aflyinghorse 2006-12-15
  • 打赏
  • 举报
回复
ref_ptr(T *pdata = NULL)构造函数加上explicit?
axx1611 2006-12-15
  • 打赏
  • 举报
回复
继续。。。。 = =

测试循环赋值错误的代码:

ref_ptr<ring> f(ref_ptr<ring> &x)
{
cout << "function f()" << endl;
x = new ring(4);
return ref_ptr<ring>(new ring(5));
}

void run()
{
ref_ptr<ring> t(new ring(1));
t = t;
cout << "mark 1" << endl;
ref_ptr<ring> s(new ring(2)), p, q;
q = s;
p = q;
s = p;
cout << "mark 2" << endl;
ref_ptr<ring> x(new ring(3));
x = f(x);
cout << "mark 3" << endl;
}

.... // 其余同前

输出:

create node {1}
release node {1}
mark 1
create node {2}
release node {2}
mark 2
create node {3}
function f()
create node {4}
release node {3}
create node {5}
release node {4}
release node {5}
mark 3
release node {5}
请按任意键继续. . .



最后那个release node {5}算是很严重的问题了。。。。
fprintf 2006-12-15
  • 打赏
  • 举报
回复
......MJ
pottichu 2006-12-15
  • 打赏
  • 举报
回复
说实在的,不喜欢楼主的代码风格,呵呵。
看你的代码是件头疼的事情.
axx1611 2006-12-15
  • 打赏
  • 举报
回复
突破自包含循环引用的测试代码:

#include <iostream>
#include <cstdlib>
#include <string>
#include "ref_ptr.h"

using namespace std;

class ring // 环形链表节点
{
public:
ring(int i) : m(i) { cout << "create node {" << m << "}" << endl; }
~ring() { cout << "release node {" << m << "}" << endl; }
ref_ptr<ring> next;
private:
int m;
};

class tree // 二叉树叶
{
public:
tree(const string &s) : m(s) { cout << "create leaf {" << m << "}" << endl; }
~tree() { cout << "release leaf {" << m << "}" << endl; }
ref_ptr<tree> left;
ref_ptr<tree> right;
private:
string m;
};

void run();

int main()
{
run();
system("pause");
};

void run()
{
// 二叉树构成东西南北环
ref_ptr<tree> north(new tree("north"));
ref_ptr<tree> south(new tree("south"));
ref_ptr<tree> west(new tree("west"));
ref_ptr<tree> east(new tree("east"));
north->left = west;
north->right = east;
south->left = east;
south->left = west;
west->left = north;
west->right = south;
east->left = south;
east->right = north;
// 5个节点的环形链表
ref_ptr<ring> head(new ring(0)), r = head;
for (int i = 1; i < 5; i++)
{
r->next = new ring(i);
r = r->next;
}
r->next = head;
}

输出:

create leaf {north}
create leaf {south}
create leaf {west}
create leaf {east}
create node {0}
create node {1}
create node {2}
create node {3}
create node {4}
release node {0}
release node {1}
release node {2}
release node {3}
release node {4}
release leaf {east}
release leaf {west}
release leaf {south}
release leaf {north}
请按任意键继续. . .



加载更多回复(47)

65,189

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧