问题:是不是在XP上可用?
1、==的判断,NULL要在前,否则报无法INT与THIS匹配
2、注意点和->
3、重载的注意点,类对象第一个函数可省略但其实是默认this,如果想做非本对象为第一位的,必须用友元的来操作。
4、没有==则LIST无法用
5、智能指针的Reset是-1,如果为0则释放,不要混了一定释放。
6、如果自定义=(拷贝),则一定要清除自己。
7、为什么STL的为直接赋0没问题,自己写的有问题。
有一个小程序,用到了智能指针,挺好用的。但是业务需要,在XP上使用这个程序,原来发现过,即使在XP上安装了运行时环境和库,也不支持相关的C++11的东西。于是进行了改写,保证可以在Xp的低版本上使用。
在改写智能指针的时候,遇到了一系列的问题,先把代码放上来,然后再一个个的分析出现的问题。
namespace mystd
{
template <typename T>
class shared_ptr {
public:
shared_ptr(T *p = 0) : ptr(p), pRef(new size_t(1)) { }
shared_ptr(const shared_ptr& src) : ptr(src.ptr), pRef(src.pRef)
{
++*pRef;
}
shared_ptr& operator= (const shared_ptr& rhs)
{
// self-assigning is also right
++*rhs.pRef;
decrUse();
ptr = rhs.ptr;
pRef = rhs.pRef;
return *this;
}
T*get() const
{
return ptr;
}
//第一种
bool operator==(const shared_ptr&rhs) const
{
if (get() == rhs.get())
{
return true;
}
else
{
return false;
}
}
//第二种
bool operator==(const int*rhs) const
{
if ( (void*)rhs == this->ptr)
{
return true;
}
else
{
return false;
}
}
//第三种
friend inline bool operator==(const int*data, const shared_ptr&rhs)
{
if ((void*)data == rhs.ptr)
{
return true;
}
else
{
return false;
}
}
T *operator->()
{
if (ptr)
{
return ptr;
}
return NULL;
}
const T *operator->() const
{
if (ptr)
{
return ptr;
}
return NULL;
}
T &operator*()
{
if (ptr)
{
return *ptr;
}
return NULL;
}
const T &operator*() const
{
if (ptr)
{
return *ptr;
}
}
void reset()
{
if (pRef != NULL && ( --*pRef == 0))
{
delete ptr;
delete pRef;
ptr = NULL;
pRef = NULL;
}
}
~shared_ptr()
{
decrUse();
#ifdef TEST_SMARTPTR
std::cout << "shared_ptr: Destructor" << std::endl; // for testing
#endif
}
private:
void decrUse()
{
--(*pRef);
if (*pRef == 0)
{
delete ptr;
delete pRef;
ptr = NULL;
pRef = NULL;
}
}
private:
T *ptr;
size_t *pRef;
};
}
首先在这里碰到的第一个问题就是==的重载,这个可是真的没想到,在这个重载中,遇到了几个代表性的问题:
1、类内重载和友元重载的区别,体现出来就是不同的对象==谁先谁后的问题。
这个问题其实是涉及到类成员函数的运算符重载,比友元重载少一个参数的问题,也就是类成员函数重载运算符,默认第一个参数是对象本身,这样,也就限制了此种重载的一个使用方式,即假如第一个参数不是对象怎么办?举一个简单的例子:
myshared_ptr<object> p;
p == NULL;//OK
NULL == p;//error
从C++的语法上来讲,上面两行代码都没有问题,但是从这里看就是有问题,因为如果使用类内部成员函数的==重载(上面标红第一种),就会报参数错误,第二种也同样,但是有了第三上就OK了。
同样只有第一种,没有第二、三种,则p==NULL也会报错。但是如果增加第三种,就没事了。
也就是说第三种,其实是最普适的,但是因为友元破坏了类的封装性,所以有的时候儿还是要取舍一下。
至于第一种没有,见下面的第4条。
2、智能指针本身的成员与指针指针指向成员的区分,也就是说点(.)和->的区别。这个错误遇到的
3、又遇到了老问题,const的不能转换,因为传入的是const,所以只好把get也改成了常函数。
4、前面也遇到过,智能指针 如果不重载==,无法在List等容器中使用。
5、对智能指针为NULL的处理。
其次是对智能指针Reset()函数的理解。错误的理解成了一调用这个函数就释放,其实它的作用只是引用引用计数减1,同时把智能指针自身变成空。
最后是对重载=运算符时,一定要判断pRef是否为0,防止重复创建引用计数器内存。
6、在调试中遇到了一个奇怪的问题,如果创建一个带智能指针的数组,那么直接用memset(array,0,sizeof(array))时,STL的智能指针没有问题,而自己写的智能指针就会出现问题,导致程序直接挂掉。
这里面还遇到了一个原来的小BUG,就是在上面的memset中,只是sizeof(array)而没有考虑Array成员的大小,其实应该是sizeof(array)*sizeof(object),但也就是这样,才发现了问题,程序有一部分可以正常使得,而有一部分无法正常使用,最终找到了原因。就是memset后,自定义的智能指针成了不可使用的。而原来这个程序使用STL的std::shared_ptr时就没有问题,而那个小BUG也就隐藏在了这其中。无语。
一定要重视细节。但不能陷入细节。
自定义智能指针的注意点之补遗
在前面的博文中,比较详细的介绍了自定义的一个智能指针,但是也留下了两个小尾巴,一个是上面说的使用这个智能指针的时候儿应用到数组中,Reset时会报错;第二个是将这个数组直接清0的情况下,也会引起程序的异常崩溃。下面分步来说:
首先说Reset函数先看定义:
void reset()
{
if (pUse != NULL && ( --*pUse <= 0))
{
delete ptr;
//delete pUse;
ptr = NULL;
*pUse = 1;
//*pUse = NULL;
}
}
请注意注释掉的绿色部分,也就是说,如果去除掉这个,则程序是正常的。百思不得其解,专门写一个例程也没有问题。后来突然想到原因,是处理的分支不够,也就是说,固定数组中这个pUse已经分配了空间,这里是不能删除的,或者换句话说,你用 Delete释放内存的方法去释放全局的数组,一定不成啊。
下断点看了下,果然如此。他们的内存空间是一样的。这样说来,应该是自定义的智能指针处理的问题不全面,应用的范围有限。没有处理好智能指针如Std中的Empty值一样。
同理,这个数组直接Memset成0后有问题,也是这个问题,对0的处理没有到位,必然是这种情况。也可以说,如果使用memset,应该重处理成Empty。
知道问题的原因后解决就简单了,直接注释掉绿色部分或者不调用Reset都可以(如果是在其它情况下不能注释,这就是下面说得适应有问题。),但适应起来还是有问题,有时间把这个自定义的智能指针丰富起来,然后形成自己的一个小库。意义虽然不大,但可以增强了解标准库中的知识。
总结一下:
1、因为处理的不全,当遍历数组Reset时,会delete pUse,那么下面再*pUse = 1;直接就挂掉了。
2、如果设置了注释的pUse = NULL;则在第一次使用完成后,数组中的*pUse = 0,则赋值后decrUse();就地释放掉智能指针当前的计数器,而前面此指针已经为空,崩溃退出。
3、处理的情况包括未发现的基本类似于上面两种,计数器判断不科学。但暂时尚未有更合理的方法。有时间学习一下标准库和BOOST库的智能指针实现的方法。
焦点实际是是对empty处理。
努力要从今日始。