很多人都说:C++比Java难,难就难在指针,指针很难学,但是不可否认,通过指针,我们确实将很多问题解决了,不过为什么要引入智能指针呢?
首先这里先声明一点:智能指针并不是指针,它的本质是个类,不过,它的功能是自动管理指针所指向的动态内存和释放工作,所以叫他智能指针。
那么为什么要引入智能指针呢?先来看一个例子:
int FunTest()
{
int *p = new int;
if (1)
{
throw 1;
}
delete p; //由于异常处理,导致delete未执行
}
int main()
{
try
{
FunTest();
}
catch (...)
{
cout << "未知异常" << endl;
}
return 0;
}
异常抛出我们都知道,上面的例子中因为异常抛出,导致delete p;未执行,从而导致内存泄漏问题,异常抛出只是改变程序执行顺序的一种情况,而类似这样的有很多,那么如何避免这样的问题呢?又回到了问题的起点,当然是智能指针的引入了。
那么接下来我们就来看几种智能指针吧!
auto_ptr
auto_ptr是C++标准库中的一个智能指针,其中包括几个成员函数,但是标准库又建议最好不要再使用auto_ptr,这是为什么?
既然有这个智能指针,而又强调不让使用,那么肯定是这个智能指针出问题了,或者是其中存在很大的缺陷,如果存在缺陷,那么是什么呢?
只看这些没什么用,我们还是来模拟一下auto_ptr吧,这样,有什么缺陷就一目了然了:
template<typename T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{}
//拷贝构造函数
AutoPtr(AutoPtr<T>&ap)
{
_ptr = new T; //先分配空间
_ptr = ap._ptr; //再资源转移
ap._ptr = NULL; //将原来的指针置空
}
~AutoPtr()
{
if (NULL != _ptr)
{
delete _ptr;
}
}
//赋值运算符重载
AutoPtr<T> operator=(AutoPtr<T>& ap)
{
if (this != &ap)
{
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
T& operator *()
{
return *_ptr;
}
T* operator ->()
{
return _ptr
}
private:
T* _ptr;
};
auto_ptr说白了就是权限的转移,在赋值运算符重载和拷贝构造函数中将资源转移,防止出现多个指针同时指向一块内存空间的现象从而防止内存被释放多次导致内存泄漏。
但是同时,这个智能指针存在很大的缺陷,我们知道资源转移之后,原来的指针被赋空,要是再进行一些使用的语句那么程序会崩的:
比如:
int main()
{
AutoPtr<int> a = new int(1);
AutoPtr<int> b(a);
*a = 2;//出现错误 1
AutoPtr<int> b(AutoPtr<int> (new int));// 2
return 0;
}
总结一下,使用auto_ptr的话,一般会出现两个问题:
一是,已经转移权限的指针要是再次被使用的时候会出现错误,因为那个指针在转移资源之后就被赋成空指针了,要是再使用,通过解引用的方法来赋值 ,显然是不行的,所以就报错了。
二是,拷贝运算符接受了常量。我们知道临时对象具有常性,(也就是不能改变),如果使用拷贝运算符的时候,将一个临时对象附进去,也就是像例子中的那样,那么就会出错。
然而,auto_ptr已经是标准库中的智能指针了,也就是已经有人使用了,或许在某些重要的领域,已经占有重要的位置了,所以,标准库肯定不会轻易将auto_ptr移除,但是确实存在问题,所以只能建议:最好不要使用了
scoped_ptr
首先声明一点:那就是, scoped_ptr 并不是C++标准库中的智能指针,而是boost库中的智能指针,不过在标准库中也有一个分身,它叫:unique_ptr。
scoped_ptr是一个很有特点的智能指针,之所以说他很有特点,从名字上就会看出来,当然不是boost库中的那个名字了,而是标准库中的那个名字,unique(唯一的),这就是它的 特点:独占一份,不允许赋值和拷贝。
那么问题来了,如何防拷贝呢?
下面我们给出三种方法,看看哪一种适合:
1、在类中给出赋值运算符的重载和拷贝构造函数的声明,但是,不给出定义;
2、在类中给出赋值运算符的重载和拷贝构造函数的定义,不过给成私有的;
3、在类中 只 给出赋值运算符的重载和拷贝构造函数的声明,并且给成私有的。
在讨论这个问题之前,我们还是将scoped_ptr 模拟实现出来,这样,我们再来讨论并验证我们的答案:
template<typename T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr)
:_ptr(ptr)
{}
~ScopedPtr()
{
if (_ptr)
{
delete _ptr;
}
}
T &operator *() const
{
if (_ptr)
{
return *_ptr;
}
}
T *operator ->() const
{
return _ptr;
}
private:
ScopedPtr(const ScopedPtr<T> & sp);
ScopedPtr<T>& operator=(const ScopedPtr<T>& sp);
private:
T *_ptr;
};
先说一下第一种吧,当我们的函数原型给出,并且是public的形式,那么我们可不可以认为,这个函数已经被给出了,因为,不管在类的内部还是外部,我们都可以将这个函数实现了,那么,防拷贝还有效吗?
显而易见第一种是不行的,那么剩下的第二种和第三种,这两种都是私有的形式,但是区别是,一个定义了,一个只是声明了,不过,定义了就有风险,因为在C++中有一个特殊的存在可以在类外访问类的私有成员,那就是友元函数,如果说懒得话,那么最好还是不写吧。
综合所述,还是只给出声明,并且给成私有的,这样可以防普通类型的同时也可以防友元。