C++没有像Java、.NET一样的垃圾回收机制,内存泄露的预防和解决一向是程序编写中的要点。
如果在一个类中进行了动态分配,一般实现“智能指针”行为或值型行为。下面是一个“智能指针”行为类的例子:
#include <iostream>
class Hello
{
public:
Hello(){std::cout<<"Hello()"<<std::endl;}
~Hello(){std::cout<<"~Hello()"<<std::endl;}
};
class World
{
public:
World(Hello*);
World(const World&);
World&operator=(const World&);
~World();
private:
struct SmartP
{
friend World;
SmartP(Hello*);
~SmartP();
unsigned use;
Hello *ptr;
};
SmartP *ptr;
};
inline World::World(Hello*p):ptr(new SmartP(p)){}
inline World::World(const World&w):ptr(w.ptr){++(ptr->use);}
inline World&World::operator=(const World&w)
{
++(w.ptr->use);
this->~World();
ptr=w.ptr;
return *this;
}
inline World::~World(){if(0==--(ptr->use))delete ptr;}
inline World::SmartP::SmartP(Hello*p):ptr(p),use(1){}
inline World::SmartP::~SmartP(){delete ptr;}
对这段代码不是很明白的同学可以参照《C++ Primer 中文版 第四版》13.5.1 定义智能指针类。
如果在程序中大量使用这种技巧,是非常劳民伤财的。于是,我们追求一种通用的解决技巧——智能指针。
template<typename T>class SmartP
{
public:
SmartP(T* =0)throw();
SmartP(const SmartP&)throw();
SmartP&operator=(const SmartP&)throw();
~SmartP()throw();
T&operator*()const throw();
T*operator->()const throw();
private:
T*p_;
unsigned*use_;
};
template<typename T>inline SmartP<T>::SmartP(T*p):p_(p),use_(new unsigned(1)){}
template<typename T>
inline SmartP<T>::SmartP(const SmartP&s):p_(s.p_),use_(s.use_){++*use_;}
template<typename T>inline SmartP<T>&SmartP<T>::operator=(const SmartP&s)
{
++*s.use_;
this->~SmartP();
p_=s.p_;
use_=s.use_;
return *this;
}
template<typename T>inline SmartP<T>::~SmartP()
{if(0==--*use_){delete p_;delete use_;}}
template<typename T>inline T&SmartP<T>::operator*()const{return *p_;}
template<typename T>inline T*SmartP<T>::operator->()const{return p_;}
在这里我们使用了使用计数分离技术。World类在使用智能指针后变成了:
class World
{
public:
World(Hello*);
private:
SmartP<Hello> ptr;
};
inline World::World(Hello*p):ptr(p){}
真的非常的简练。
在这之后的使用过程中,我发现了一个BUG。
class Hello
{
public:
Hello(){std::cout<<"Hello()"<<std::endl;}
~Hello(){std::cout<<"~Hello()"<<std::endl;}
void Func(){SmartP<Hello> p=this;}
};
void Test()
{
SmartP<Hello> p=new Hello();
p->Func();
}
这会导致重复释放,一个危险的问题。原计划在智能指针中加一个全局Map变量来解决这个问题。但是,由于全局依赖问题和效率问题,改为将使用计数与对象捆绑在一起。那样的话,找到动态分配的对象的同时找到了使用计数变量。
我们知道,多态使用的前提是:使用指向派生类对象的基类指针(或引用派生类对象的引用)调用基类的virtual函数。但是,我们设计的智能指针却没有这个功能。这个问题有两种解决方案。第一种解决方案:重载默认转换操作符。第二种解决方案:重载构造函数和赋值操作符。为了使软件可维护,我们采用第一种解决方案。
这两个问题的同时解决使得下面的代码有一定的难度。
template<typename T>class SmartP
{
public:
SmartP(T*)throw();
SmartP(T*,unsigned)throw();
SmartP(const SmartP&)throw();
SmartP&operator=(const SmartP&)throw();
~SmartP()throw();
T&operator*()const throw();
T*operator->()const throw();
template<typename T>operator SmartP<T>()const throw();
private:
unsigned&Count()const throw();
T*p_;
};
template<typename T>inline SmartP<T>::SmartP(T*p):p_(p){++Count();}
template<typename T>inline SmartP<T>::SmartP(T*p,unsigned):p_(p){}
template<typename T>inline SmartP<T>::SmartP(const SmartP&s):p_(s.p_){++Count();}
template<typename T>inline SmartP<T>&SmartP<T>::operator=(const SmartP&s)
{
++s.Count();
this->~SmartP();
p_=s.p_;
return *this;
}
template<typename T>inline SmartP<T>::~SmartP()
{if(0==--Count()){p_->~T();delete &Count();}}
template<typename T>inline T&SmartP<T>::operator*()const{return *p_;}
template<typename T>inline T*SmartP<T>::operator->()const{return p_;}
template<typename T>
inline unsigned&SmartP<T>::Count()const{return *((unsigned*)(p_)-1);}
template<typename T0>template<typename T1>
SmartP<T0>::operator SmartP<T1>()const{return SmartP<T1>(p_);}
#define NEW(T,P) /
SmartP<T>(new(new(new unsigned char[sizeof(unsigned)+sizeof(T)])/
unsigned(1)+1)T P,0)
很多时候,我们在定义一个智能指针的时候,没有必要立即用它指向一个对象。这是“委托模式”的一个事例。仔细的读者已经发现我们的智能指针现在不具有这种功能了。
很多时候,我们在智能指针的生命期结束之前,需要将使用计数减一。其中一部分情况是为了效率,另一部分是为了实现类似流对象的close函数的功能。
template<typename T>class SmartP
{
public:
SmartP()throw();
SmartP(T*)throw();
SmartP(T*,unsigned)throw();
SmartP(const SmartP&)throw();
SmartP&operator=(const SmartP&)throw();
~SmartP()throw();
T&operator*()const throw();
T*operator->()const throw();
template<typename T>operator SmartP<T>()const throw();
private:
unsigned&Count()const throw();
T*p_;
};
template<typename T>inline SmartP<T>::SmartP():p_(0){}
template<typename T>inline SmartP<T>::SmartP(T*p):p_(p){++Count();}
template<typename T>inline SmartP<T>::SmartP(T*p,unsigned):p_(p){}
template<typename T>
inline SmartP<T>::SmartP(const SmartP&s):p_(s.p_){if(p_)++Count();}
template<typename T>inline SmartP<T>&SmartP<T>::operator=(const SmartP&s)
{
if(s.p_)++s.Count();
this->~SmartP();
p_=s.p_;
return *this;
}
template<typename T>inline SmartP<T>::~SmartP()
{if(p_&&0==--Count()){p_->~T();delete &Count();}p_=0;}
template<typename T>inline T&SmartP<T>::operator*()const{return *p_;}
template<typename T>inline T*SmartP<T>::operator->()const{return p_;}
template<typename T>
inline unsigned&SmartP<T>::Count()const{return *((unsigned*)(p_)-1);}
template<typename T0>template<typename T1>
SmartP<T0>::operator SmartP<T1>()const{return SmartP<T1>(p_);}
#define NEW(T,P) /
SmartP<T>(new(new(new unsigned char[sizeof(unsigned)+sizeof(T)])/
unsigned(1)+1)T P,0)
下面的代码可以看做是SmartP的简单测试。
class Hello
{
public:
Hello(){SmartP<Hello>p=this;std::cout<<"Hello()"<<std::endl;}
virtual ~Hello(){std::cout<<"~Hello()"<<std::endl;}
void Func(){SmartP<Hello>p=this;std::cout<<"Func"<<std::endl;}
};
class World:public Hello
{
public:
World(){std::cout<<"World()"<<std::endl;}
~World(){std::cout<<"~World()"<<std::endl;}
};
void Test()
{
SmartP<Hello> p;
SmartP<Hello> p0=NEW(World,);
SmartP<World> p1=NEW(World,);
p0=p1;
SmartP<Hello> p2(p1);
SmartP<Hello> p3=p1;
p3.~SmartP();
p0->Func();
p=p0;
p=p3;
}
还有一类问题——循环引用。这个问题使用引用计数无法解决。但是,我们的智能指针允许手动释放循环引用。下面是一个小例:
class World;
class Hello
{
public:
Hello(){std::cout<<"Hello()"<<std::endl;}
virtual ~Hello(){std::cout<<"~Hello()"<<std::endl;}
SmartP<World> p_;
};
class World
{
public:
World(){std::cout<<"World()"<<std::endl;}
~World(){std::cout<<"~World()"<<std::endl;}
SmartP<Hello> p_;
};
void Test()
{
SmartP<Hello> p=NEW(Hello,);
p->p_=NEW(World,);
p->p_->p_=p;
p->p_.~SmartP();
}
到目前为止,我们的智能指针解决了我发现的所有问题。
如果,读者发现了新的问题,请发送邮件到 zhanglibin_1222@163.com ,或者到我的博客 http://blog.csdn.net/zhanglibin_1222 进行讨论。