一个智能指针简单实现,各位帮看看有没有可能产生内存泄露的地方,以及给点指点

cnStreamlet 2009-03-25 10:46:15
xlPointer.h

#ifndef __XLPOINTER_H_B0788703_ABD1_457D_8FEC_E527581FD9EF_INCLUDED__
#define __XLPOINTER_H_B0788703_ABD1_457D_8FEC_E527581FD9EF_INCLUDED__


namespace xl
{

#ifndef NULL
#define NULL 0
#endif

/// @brief Smart Pointer.
template <typename T>
class QIPtr
{
public:
/**
* @brief Default constructor.
*/
QIPtr();

/**
* @brief Constructor. Must give an heap address. Sample use: QIPtr<int> p = new int;.
* @param pData [in] A heap address, usually returned by operator new.
* @remark operator delete must not be called, if using QIPtr.
*/
QIPtr(T *pData);

/**
* @brief Copy construction.
* @param that [in] The pointer to be copied.
*/
QIPtr(const QIPtr<T> &that);

/**
* @brief Destroyer. Inside this function, the heap address will be released if there is no more references.
*/
~QIPtr();
public:

/**
* @brief Operator *, use it as usual.
* @return return a reference of T-typed object.
*/
T &operator*() const;

/**
* @brief Operator ->, use it as usual.
* @return return the address of the object.
*/
T *operator->() const;

/**
* @brief Copy operator, use it as usual.
* @param that [in] The pointer to be copied.
* @return Reference of this object
*/
QIPtr<T> &operator=(const QIPtr<T> &that);

/**
* @brief Copy operator, use it as usual.
* @param that [in] The pointer to be copied.
* @return Return true if the two points equals, return false otherwise.
*/
bool operator==(const QIPtr<T> &that) const;

/**
* @brief Copy operator, use it as usual.
* @param that [in] The pointer to be copied.
* @return Return true if the two points do not equals, return false otherwise.
*/
bool operator!=(const QIPtr<T> &that) const;

private:
// Do not use these functions.

private:
T *m_pData;
size_t *m_pcRefs;
};

template <typename T>
QIPtr<T>::QIPtr()
{
this->m_pData = NULL;
this->m_pcRefs = new size_t;
*this->m_pcRefs = 0;
}

template <typename T>
inline QIPtr<T>::QIPtr(T *pData)
{
this->m_pData = pData;
this->m_pcRefs = new size_t;
*this->m_pcRefs = 1;
}

template <typename T>
QIPtr<T>::QIPtr(const QIPtr<T> &that)
{
this->m_pData = that.m_pData;
this->m_pcRefs = that.m_pcRefs;
++*this->m_pcRefs;
}

template <typename T>
QIPtr<T>::~QIPtr()
{
if (--*this->m_pcRefs == 0)
{
delete this->m_pcRefs;

if (this->m_pData != NULL)
{
delete this->m_pData;
}
}
}

template <typename T>
inline T &QIPtr<T>::operator*() const
{
return *this->m_pData;
}

template <typename T>
inline T *QIPtr<T>::operator->() const
{
return this->m_pData;
}

template <typename T>
QIPtr<T> &QIPtr<T>::operator=(const QIPtr<T> &that)
{
if (this == &that)
{
return *this;
}

if (this->m_pData == that.m_pData)
{
++*this->m_pcRefs;
}
else
{
this->~QIPtr();

this->m_pData = that.m_pData;
this->m_pcRefs = that.m_pcRefs;
++*this->m_pcRefs;
}

return *this;
}

template <typename T>
bool QIPtr<T>::operator==(const QIPtr<T> &that) const
{
return this->m_pData == that.m_pData;
}

template <typename T>
bool QIPtr<T>::operator!=(const QIPtr<T> &that) const
{
return this->m_pData != that.m_pData;
}


} // namespace xl

#endif // #ifndef __XLPOINTER_H_B0788703_ABD1_457D_8FEC_E527581FD9EF_INCLUDED__


DebugNew.h

#ifndef __DEBUGNEW_H_33164B60_A660_4B88_AEA3_270C97F24B12_INCLUDED__
#define __DEBUGNEW_H_33164B60_A660_4B88_AEA3_270C97F24B12_INCLUDED__


#ifdef __cplusplus

#ifdef _DEBUG

#include <crtdbg.h>

// Overload operator new
inline void * __cdecl operator new(size_t nSize, const char * lpszFileName, int nLine)
{
int nFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
nFlag |= _CRTDBG_LEAK_CHECK_DF;
_CrtSetDbgFlag(nFlag);

return ::operator new(nSize, _NORMAL_BLOCK, lpszFileName, nLine);
};

#define DEBUG_NEW new(__FILE__, __LINE__)
#define new DEBUG_NEW

// Overload operator delete (, or the compiler will give warning.)
inline void __cdecl operator delete(void *pData, const char * lpszFileName, int nLine)
{
::operator delete(pData);
}

#endif // #ifdef _DEBUG

#endif // #ifdef __cplusplus


#endif // #ifndef __DEBUGNEW_H_33164B60_A660_4B88_AEA3_270C97F24B12_INCLUDED__



Test.cpp

#include "DebugNew.h"
//#include <assert.h>

#include "xlPointer.h"
using namespace xl;

int main()
{
QIPtr<int> c = new int;
*c = 3;

QIPtr<int> b = c;
*b = 8;

b = new int;
*b = 4;

*c = 8;

int d = *b;

return 0;
}


谢谢~
...全文
367 23 打赏 收藏 转发到动态 举报
写回复
用AI写文章
23 条回复
切换为时间正序
请发表友善的回复…
发表回复
ToBeTough 2009-03-27
  • 打赏
  • 举报
回复
先收之
yinlijun610 2009-03-27
  • 打赏
  • 举报
回复
定下
cnStreamlet 2009-03-26
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 threeleafzerg007 的回复:]
不过,如果有大量的小对象需要构造析构的话,对于类型 T 你可以重载其 operator new 来使用内存池策略,而 那个ref就没有办法了。
如果是我的话,我会在QIPtr维护一份链表类似map <T*,refCount>之类的,当然代价是空间的增长,以及一些性能损失。
[/Quote]

在 QIPtr 维护一份引用计数表我前几天也想到过,几乎认为是唯一的解决办法了,但是还没去做;后来又看到了一种解决方案,就是我现在用的。相比之下,我觉得现在这样子比较符合我的意愿,显得比较轻量。


=====
说些题外话,对于 STL,我现在还有一种莫名其妙的排斥情节,觉得它只是一个普通的库,没有“它是 C++ 的一部分”的感觉,而那些 stdio.h、string.h 我却觉得格外亲切。不知为什么,克服不了……
cnStreamlet 2009-03-26
  • 打赏
  • 举报
回复
[Quote=引用 20 楼 threeleafzerg007 的回复:]
这种是C 天然 短路 或者是侯捷嘴里的骤死式

当然用

这也是 sccot meyers 为何强调 不要 重载 && || 的原因之一 因为你无法实现 编译器能实现的东西


比如 你想要的 判断是栈 或者是 堆上的对象

你可以跟踪一下 realloc 看看会不会 追到一个函数 (用于判断第一个参数的)来判断指针是不是来自于堆上的。
(编译器可以实现,你不行,或者你可以 但却是不可移植代码)
[/Quote]

赞!给了很多启发,谢谢~!
liumingrong 2009-03-26
  • 打赏
  • 举报
回复
你的智能指针实现中,除了引用计数外,只保存了一个模板类型的指针,比如sample1*,这样删除sample2对象的话
需要用户保证基类的析构函数是虚的(通常理应这样做)。
如果要求在用户没有将析构函数声明为虚函数的情况下仍然运行正常,
一种可能的办法是像boost shared_ptr那样再保存一个实际类型的指针,比如sample2*,这个指针用来删除对象,
这样甚至不需要基类的析构函数是公有的(当然至少是保护的)
在不存在继承体系的情况下,两个指针的类型是相同的

对于int a; QIPtr <int> p(&a);这种情况几乎没有办法,boost也没有解决
用户非要那么做就等于他调用delete &a;出错也是活该了
小建议:QIPtr(T *pData); => explicit QIPtr(T *pData);

---------------------------------------------------------------------
C/C++ code
class sample1
{
int *a;
public:
sample1()
{
a = new int;
}
virtual ~sample1()
{
delete a;
}
};
class sample2 : public sample1
{
int *b;
public:
sample2()
{
b = new int;
}
~sample2()
{
delete b;
}
};
QIPtr<sample1> x = new sample2;
类似这样的暂时没有问题。如果 sample1 析构函数非虚,那是会有问题,但我觉得这个责任应该归到 sample1 上。
但是我对继承产生的影响并不熟悉,我觉得可能没那么简单,请再给点提示吧,如果有空并方便的话,谢了^_^
另外,有没有办法判断堆地址和栈地址?我想阻止这样的操作:int a; QIPtr <int> p = &a 。
threeleafzerg007 2009-03-26
  • 打赏
  • 举报
回复
这种是C 天然 短路 或者是侯捷嘴里的骤死式

当然用

这也是 sccot meyers 为何强调 不要 重载 && || 的原因之一 因为你无法实现 编译器能实现的东西


比如 你想要的 判断是栈 或者是 堆上的对象

你可以跟踪一下 realloc 看看会不会 追到一个函数 (用于判断第一个参数的)来判断指针是不是来自于堆上的。
(编译器可以实现,你不行,或者你可以 但却是不可移植代码)
cnStreamlet 2009-03-26
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 threeleafzerg007 的回复:]
是,第一条,我有欠考虑。
不过,如果有大量的小对象需要构造析构的话,对于类型 T 你可以重载其 operator new 来使用内存池策略,而 那个ref就没有办法了。
如果是我的话,我会在QIPtr维护一份链表类似map <T*,refCount>之类的,当然代价是空间的增长,以及一些性能损失。


if (this->m_pData == that.m_pData) //这里指针指向同一地址,为什么还要++?
{
++*this->m_pcRefs;

[/Quote]

对,这个是我没考虑好。早上的时候关于这个我想回复的,可是一直回复失败,提示“哥们别灌水了”= =
threeleafzerg007 2009-03-26
  • 打赏
  • 举报
回复
是,第一条,我有欠考虑。
不过,如果有大量的小对象需要构造析构的话,对于类型 T 你可以重载其 operator new 来使用内存池策略,而 那个ref就没有办法了。
如果是我的话,我会在QIPtr维护一份链表类似map<T*,refCount>之类的,当然代价是空间的增长,以及一些性能损失。


if (this->m_pData == that.m_pData) //这里指针指向同一地址,为什么还要++?
{
++*this->m_pcRefs;
}
这个确实有必要吗?
比如,如下代码:

QIPtr <int> a = new int;
QIPtr <int> b = a;
b = a;
b = a;
b = a;
那引用计数
岂不是5?





cnStreamlet 2009-03-26
  • 打赏
  • 举报
回复
[Quote=引用 18 楼 threeleafzerg007 的回复:]
就是有的地方 代码不够简洁

如果我来写

template <typename T>
inline void QIPtr <T>::Release()
{
if (this->m_pcRefs != NULL && --*this->m_pcRefs == 0)
{
delete this->m_pcRefs;
delete this->m_pData;
this->m_pcRefs = NULL,this->m_pData = NULL;
}
}

c++ delete NULL,合法。另暗含了 m_pcRefs为NULL,m_pData直接不需要判断。

[/Quote]

嗯,,,我只是想把判断写得显得有步骤点……有些地方确实是也显得多余。
delete NULL 合法我知道的,我还一直有在避免 delete NULL 的习惯,不知道有没有意义?

另外,对于条件判断中,利用 && 的前一条件为 false 不会再去判断后一条件 这一特性,好吗?大家平时都倾向于利用这个特性吗?

threeleafzerg007 2009-03-26
  • 打赏
  • 举报
回复
我觉得应该没啥大问题 就是有的地方 代码不够简洁
template <typename T>
inline void QIPtr<T>::Release()
{
if (this->m_pcRefs == NULL)
{
return;
}

if (--*this->m_pcRefs > 0)
{
return;
}

delete this->m_pcRefs;

if (this->m_pData == NULL)
{
return;
}

delete this->m_pData;

}

如果我来写

template <typename T>
inline void QIPtr<T>::Release()
{
if (this->m_pcRefs != NULL & --*this->m_pcRefs == 0)
{
delete this->m_pcRefs;
delete this->m_pData;
this->m_pcRefs = NULL,this->m_pData = NULL;
}
}

看上去是不是会舒服很多呢?
c++ delete NULL,合法。另暗含了 m_pcRefs为NULL,m_pData直接不需要判断。

cnStreamlet 2009-03-26
  • 打赏
  • 举报
回复

#ifndef __XLQIPTR_H_B0788703_ABD1_457D_8FEC_E527581FD9EF_INCLUDED__
#define __XLQIPTR_H_B0788703_ABD1_457D_8FEC_E527581FD9EF_INCLUDED__


namespace xl
{

#ifndef NULL
#define NULL 0
#endif

/// @brief Smart Pointer.
template <typename T>
class QIPtr
{
public:
/**
* @brief Default constructor.
*/
QIPtr();

/**
* @brief Constructor. Must give an heap address. Sample use: QIPtr<int> p(new int);.
* @param pData [in] A heap address, usually returned by operator new.
* @remark operator delete must not be called, if using QIPtr.
*/
explicit QIPtr(T *pData);

/**
* @brief Copy construction.
* @param that [in] The pointer to be copied.
*/
QIPtr(const QIPtr<T> &that);

/**
* @brief Destroyer. Inside this function, the heap address will be released if there is no more references.
*/
~QIPtr();
public:

/**
* @brief Operator *, use it as usual.
* @return return a reference of T-typed object.
*/
T &operator*() const;

/**
* @brief Operator ->, use it as usual.
* @return return the address of the object.
*/
T *operator->() const;

/**
* @brief Copy operator, use it as usual.
* @param that [in] The pointer to be copied.
* @return Reference of this object
*/
QIPtr<T> &operator=(const QIPtr<T> &that);

/**
* @brief Compare operator, use it as usual.
* @param that [in] The pointer to be compared.
* @return Return true if the two points equals, return false otherwise.
*/
bool operator==(const QIPtr<T> &that) const;

/**
* @brief Compare operator, use it as usual.
* @param that [in] The pointer to be compared.
* @return Return true if the two points do not equals, return false otherwise.
*/
bool operator!=(const QIPtr<T> &that) const;

private:
void AddRef();
void Release();

private:
T *m_pData;
size_t *m_pcRefs;
};

template <typename T>
inline void QIPtr<T>::AddRef()
{
if (this->m_pcRefs == NULL)
{
this->m_pcRefs = new size_t;
*this->m_pcRefs = 0;
}

++*this->m_pcRefs;
}

template <typename T>
inline void QIPtr<T>::Release()
{
if (this->m_pcRefs == NULL)
{
return;
}

if (--*this->m_pcRefs > 0)
{
return;
}

delete this->m_pcRefs;

if (this->m_pData == NULL)
{
return;
}

delete this->m_pData;
}


template <typename T>
inline QIPtr<T>::QIPtr() : m_pData(NULL), m_pcRefs(NULL)
{
}

template <typename T>
inline QIPtr<T>::QIPtr(T *pData) : m_pData(NULL), m_pcRefs(NULL)
{
this->m_pData = pData;
this->AddRef();
}

template <typename T>
inline QIPtr<T>::QIPtr(const QIPtr<T> &that) : m_pData(NULL), m_pcRefs(NULL)
{
this->m_pData = that.m_pData;
this->m_pcRefs = that.m_pcRefs;
this->AddRef();
}

template <typename T>
inline QIPtr<T>::~QIPtr()
{
this->Release();
}

template <typename T>
inline T &QIPtr<T>::operator*() const
{
return *this->m_pData;
}

template <typename T>
inline T *QIPtr<T>::operator->() const
{
return this->m_pData;
}

template <typename T>
inline QIPtr<T> &QIPtr<T>::operator=(const QIPtr<T> &that)
{
//if (this == &that)
//{
// return *this;
//}

if (this->m_pData == that.m_pData)
{
return *this;
}

this->Release();

this->m_pData = that.m_pData;
this->m_pcRefs = that.m_pcRefs;
this->AddRef();

return *this;
}

template <typename T>
bool QIPtr<T>::operator==(const QIPtr<T> &that) const
{
return this->m_pData == that.m_pData;
}

template <typename T>
bool QIPtr<T>::operator!=(const QIPtr<T> &that) const
{
return this->m_pData != that.m_pData;
}


} // namespace xl

#endif // #ifndef __XLQIPTR_H_B0788703_ABD1_457D_8FEC_E527581FD9EF_INCLUDED__


根据楼上几位朋友的建议现在改成这样子了
大家再看看?
pushware 2009-03-26
  • 打赏
  • 举报
回复
哦,明白了,引用计数也是共享的。:-)
cnStreamlet 2009-03-26
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 pushware 的回复:]
;==========>指针复制,浅拷贝可能会导致悬挂指针。
[/Quote]

就是要既浅拷贝,又保证不出现内存泄露及指针悬挂嘛
pushware 2009-03-26
  • 打赏
  • 举报
回复
template <typename T>
QIPtr<T> &QIPtr<T>::operator=(const QIPtr<T> &that)
{
if (this == &that)
{
return *this;
}

if (this->m_pData == that.m_pData)
{
++*this->m_pcRefs;
}
else
{
this->~QIPtr();

this->m_pData = that.m_pData;==========>指针复制,浅拷贝可能会导致悬挂指针。
this->m_pcRefs = that.m_pcRefs;
++*this->m_pcRefs;
}

我觉得问题挺多的。

return *this;
}
lan_6373836 2009-03-26
  • 打赏
  • 举报
回复

template <typename T>
QIPtr<T> &QIPtr<T>::operator=(const QIPtr<T> &that)
{
if (this == &that)
{
return *this;
}

if (this->m_pData == that.m_pData)
{
++*this->m_pcRefs;
}
else
{
this->~QIPtr();

this->m_pData = that.m_pData;
this->m_pcRefs = that.m_pcRefs;//应为复制过去了,指针的个数也就过去了
++*this->m_pcRefs;//这句的就不应该要了
}

return *this;
}

cnStreamlet 2009-03-25
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 threeleafzerg007 的回复:]
修改意见
1. size_t *m_pcRefs 完全没必要 动态分配 还要浪费 new delete
[/Quote]

谢谢。

先说第一个,后面的我慢慢体会了再来回你。
如果不动态分配的话,如何做到对某个对象计数呢?
如果发生
QIPtr<int> a = new int;
QIPtr<int> b = a;
QIPtr<int> c = b;
我怎么让 a 中的计数传给 b、又从 b 传给 c 后,a 中计数仍然同步呢?

cnStreamlet 2009-03-25
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 downmooner 的回复:]
C++PRIMERs 425页的智能指针的operator=操作 你看看
[/Quote]

嗯,我明天好好看看
cnStreamlet 2009-03-25
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 tangshuiling 的回复:]
C/C++ code
带引用计数的smartptr class,考虑下面的情况:
using namespace x1;
class sample1 {};
class sample2:public sample1 {};
QIPtr<sample1> c = new sample2;
[/Quote]

谢谢提示!你说的这个让我觉得可能有重大问题
刚才我粗粗的试了一下:

class sample1
{
int *a;
public:
sample1()
{
a = new int;
}
virtual ~sample1()
{
delete a;
}
};
class sample2 : public sample1
{
int *b;
public:
sample2()
{
b = new int;
}
~sample2()
{
delete b;
}
};
QIPtr<sample1> x = new sample2;


类似这样的暂时没有问题。如果 sample1 析构函数非虚,那是会有问题,但我觉得这个责任应该归到 sample1 上。

但是我对继承产生的影响并不熟悉,我觉得可能没那么简单,请再给点提示吧,如果有空并方便的话,谢了^_^

另外,有没有办法判断堆地址和栈地址?我想阻止这样的操作:int a; QIPtr<int> p = &a 。

downmooner 2009-03-25
  • 打赏
  • 举报
回复
C++PRIMERs 425页的智能指针的operator=操作 你看看
cnStreamlet 2009-03-25
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 downmooner 的回复:]
template <typename T>
QIPtr <T> &QIPtr <T>::operator=(const QIPtr <T> &that)
{
if (this == &that) 你这个没必要吧。
{
return *this;
}

if (this->m_pData == that.m_pData) //这里指针指向同一地址,为什么还要++?
{
++*this->m_pcRefs;
}
else
{
this->~QIPtr(); …
[/Quote]

我的设想是,
如果智能指针之间有互相赋值,那么加引用计数,所以指向了同一地址才去++,调用了析构函数并不一定会产生 delete,可能仅仅是引用计数减 1。
加载更多回复(3)

65,211

社区成员

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

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