今天看了More Effective C++附2中关于auto_ptr的实现,试了一下发现了个很有趣的问题,代码如下:
template<class T>
class auto_ptr {
public:
explicit auto_ptr(T *p = 0): pointee(p) {}
template<class U>
auto_ptr(auto_ptr<U>& rhs): pointee(rhs.release()) {}
template<class U>
auto_ptr<T>& operator=(auto_ptr<U>& rhs)
{
// VS2005编译出错
//if (this != &rhs) reset(rhs.release());
// 更改如下
reset(rhs.release());
return *this;
}
~auto_ptr() { delete pointee; }
T& operator*() const { return *pointee; }
T* operator->() const { return pointee; }
T* get() const { return pointee; }
T* release()
{
T *oldPointee = pointee;
pointee = 0;
return oldPointee;
}
void reset(T *p = 0)
{
if (pointee != p) {
delete pointee;
pointee = p;
}
}
private:
T *pointee;
// VS2005编译不过,也没看出什么用处,注掉
//template<class U> friend class auto_ptr<U>;
};
class auto_ptr {
public:
explicit auto_ptr(T *p = 0): pointee(p) {}
template<class U>
auto_ptr(auto_ptr<U>& rhs): pointee(rhs.release()) {}
template<class U>
auto_ptr<T>& operator=(auto_ptr<U>& rhs)
{
// VS2005编译出错
//if (this != &rhs) reset(rhs.release());
// 更改如下
reset(rhs.release());
return *this;
}
~auto_ptr() { delete pointee; }
T& operator*() const { return *pointee; }
T* operator->() const { return pointee; }
T* get() const { return pointee; }
T* release()
{
T *oldPointee = pointee;
pointee = 0;
return oldPointee;
}
void reset(T *p = 0)
{
if (pointee != p) {
delete pointee;
pointee = p;
}
}
private:
T *pointee;
// VS2005编译不过,也没看出什么用处,注掉
//template<class U> friend class auto_ptr<U>;
};
带有模板参数的成员拷贝构造函数和赋值操作符(见粗斜体字代码)能够方便兼容类型的赋值和拷贝构造操作,比如我们有两个具有继承关系的类Base和Derived:
class Base
{
public:
virtual ~Base() { cout << "Base::~Base()" << endl; }
};
class Derived : public Base
{
public:
~Derived() { cout << "Derived::~Derived()" << endl; }
};
{
public:
virtual ~Base() { cout << "Base::~Base()" << endl; }
};
class Derived : public Base
{
public:
~Derived() { cout << "Derived::~Derived()" << endl; }
};
如下操作之所以能够进行完全受益于这两个模板成员函数(下面的spd1和spb1、spd2和spb2都称为兼容类型变量):
auto_ptr<Derived> spd1(new Derived);
// 兼容类型拷贝构造
auto_ptr<Base> spb1(spd1);
auto_ptr<Derived> spd2(new Derived);
auto_ptr<Base> spb2; // 兼容类型赋值
spb2 = spd2;
// 兼容类型拷贝构造
auto_ptr<Base> spb1(spd1);
auto_ptr<Derived> spd2(new Derived);
auto_ptr<Base> spb2; // 兼容类型赋值
spb2 = spd2;
然而,More Effective C++附2中的auto_ptr并未提供缺省的拷贝构造和赋值操作,这使得相同类型的auto_ptr变量之间的这两个操作发生了不一致的问题,如下所示:
auto_ptr<Base> spb1(new Base);
// 同类型拷贝构造(可利用模板拷贝构造函数)
auto_ptr<Base> spb2(spb1);
// 同类型赋值(不可利用模板赋值成员函数)
spb3 = spb2;
// 同类型拷贝构造(可利用模板拷贝构造函数)
auto_ptr<Base> spb2(spb1);
auto_ptr
<Base> spb3;// 同类型赋值(不可利用模板赋值成员函数)
spb3 = spb2;
如上面代码注释所示,编译器并未匹配成员模板赋值操作成员函数而是调用了编译器给出的默认版本,这导致的问题显而易见的,它破坏了auto_ptr的原有语义,在变量退出作用域后auto_ptr所管理的资源将被释放不止一次。stl中的实现版本为之提供了缺省拷贝构造和赋值实现。
小结
本文仅借auto_ptr说事儿,关于智能指针的实现有很多参考如:boost的share_ptr,MFC的Comptr,stl的auto_ptr,Loki的SmartPtr,VOCAL里也有个Sptr……里面都涉及什么引用计数、类型检查和转换、线程安全之类的问题,这里没必要重复。
本文关注的正如题目所示,其实我并没搞明白怎么回事,如果那位仁兄知道高诉下小弟到更好了。但是,记录下这个细节,在以后的类似代码中多加注意,避免出现类似的隐晦错误是必要的。