作者:几冬雪来
时间:2024年4月7日
内容:C++内容智能指针讲解
目录
前言:
在上一篇博客中我们对C++中的C++11进行了讲解,而在C++11的讲解结束之后,接下来我们将学习C++中的另一个知识点,那就是C++中的智能指针。
为什么需要智能指针:
在C语言时期我们就学习过了指针这个概念,而C++中有引入了智能指针,那么智能指针对比平常指针有什么区别,为什么会有智能指针的存在?
在之前我们有说过,Java在一定程度上有借鉴C语言的代码,但是这里Java中并没有智能指针的存在。
在学习C++时期,我们有学习到异常这个概念,而解决异常的方法就是代码中的try...catch语句去解决这个问题。
这里有一个问题,就是每当我们有一个函数需要进行抛异常的话,在代码中就要书写一句try...catch的代码。
但是如果如上图,有4个或者多个代码需要抛异常操作,在这个地方就需要写多个try...catch语句,这种写法会使得代码的可读性大幅度的下降。
而要解决这个问题,这里就需要使用到智能指针这个概念。
普通智能指针书写:
在上面我们讲解了C++中会运用到智能指针的地方,总结来说,智能指针在多个函数需要抛异常的情况,在这个地方就需要使用到智能指针。
接下来就是智能指针的书写方法。
这里就是我们智能指针代码的书写,它的原理就是利用了构造函数与析构函数的特性。
因为构造函数与析构函数在C++的特性保证了其对象初始化自动调用构造函数,出作用域自动调用析构函数。
而因为这个特性,导致在调用的函数中,f函数正常结束或者f函数中的div函数出现抛异常的问题,这里都能调用它的析构函数。
在这里我们还可以将此处的代码进行一个简化的操作。
在这个地方我们对函数的代码进行了简化的操作,与此同时也增加了另外3个同类型的代码,每个函数的后面都存在div函数来输出是否除0错误。
这里就不需要手动对其delete,它在这个地方会自动的调用析构函数,在析构函数中会将其delete操作。
如上图,如果new处出现抛异常的问题的话,在这里不进行构造,直接运行而后到外边进行捕捉。如果是sp2抛异常的话,sp1正常调用析构。同理,如果sp3抛异常,sp2与sp1正常析构,要是函数div抛异常则三者都正常调用析构。
这就是智能指针的用法,但是这里的智能指针并不是只对pair使用,因此代码需要进行改善。
像这里想要解决问题实现更多类型的智能指针,这个地方就需要使用模版去完成。
在此处进行修改之后,下边的调用函数的类型也需要进行一定的修改。
而智能指针的机制也被称作为RAII,下图也说明了RAII的作用。
简单来说,就是将资源交给对象管理,在对象的生命周期内,资源有效,对象的生命周期到了就释放资源。
到这里我们对C++中的智能指针了解,但是这并不是指智能指针到这里就结束了,智能指针还有一些需要学习的东西。
在这里如果想要打印pair或者string后面的值,这里要怎么做?
这里如果直接用指针返回是行不通的,因为无论是sp1或者sp2会sp3都是对象,而对象不能这样打印。
但是因为智能指针的缘故,这里智能指针有两个作用,一个是管控资源的释放,另一个是让它像指针一样。
这里要进行的操作就是重载它的运算符,这样就能正常的取值。
但是这里还有一个问题,这里的运算符重载能运用在string类型中,但是这里还存在着一个问题,那就是sp1的问题。
如上图,因此运算符的重载,这里可以对取出sp3中的值,但是sp1中的值不能取出,这是因为sp1的类型的pair,属于自定义类型的指针,*sp1类似一个pair它不支持流插入。
这里可以写流插入的重载,但是又因为库中的类型不能去动它,因此像pair*等,我们还能用->去书写代码。
到这里我们大概的智能指针就压迫结束了,但是由智能指针所引申出来的一系列更难的问题需要我们了解。
接下来我们来看几个场景。
首先就是最熟悉的场景,从这里可以看出来,这里的问题就是浅拷贝的问题,析构了两次,同时这个地方还存在内存泄漏的问题。
因此在智能指针时候说的,智能指针就没有内存泄漏的结果是错误的。
只有正确的使用智能指针才不会有内存的泄漏,如果错误的使用智能指针还是会有内存泄漏的问题存在。
像这里我们对一块空间进行了两次析构,但是这里的空间其实有两块空间,有一块可能没有被释放。
这是因为没有显示的写赋值,编译器会显示的写一份默认的,而这份默认的就是浅拷贝的。
但是像解决这个问题,深拷贝又是做不到的,举个例子,如果要拷贝的内容是一个数组,我们并不知道该数组有多大,里面只有一个指针的存在。
这里只能进行浅拷贝,因为此处模拟的是原生指针,原生指针赋值是浅拷贝,也就是两块指针指向同一块空间。
在上边讲到过,智能指针的RAII有几层作用,第一层是RAII的管理资源释放,第二层是指针,而第三层也是最复杂和最难问题之一也就是拷贝问题。
并且这个问题也会牵扯出一段发展历史。
智能指针发展历史与各种智能指针:
到这里也该讲讲C++中智能指针的发展历史了,这里智能指针一般是在库中memory的头文件里边且C++的智能指针并不是从C++11才出现的,早在C++98中,C++就引入了智能指针的存在。
在C++98时期诞生的智能指针有一个独属于它的名字——auto_ptr。
这里就是我们的auto_ptr的智能指针的代码书写方法,它的写法和我们上面书写的智能指针比较相似。
即使ap3是拷贝操作,这里编译器也是正确进行了释放的操作。
那么auto_ptr在这里做了些什么?这里就需要调试来观察。
联系上文,智能指针的第三层的问题是拷贝的问题,而auto_ptr是一种解决拷贝问题的方式,那么它是怎么样解决拷贝的?
这里在上面可以看到,在拷贝的过程中进行了一个操作,也就是我们所说的管理权转移。
管理权转移从图中可以看出,原本new A(1)的资源是由ap1对象管理的,现在用ap1去拷贝到ap3,这里ap1就会被悬空,相当于ap1将它的资源的管理权给ap3。
这里如果ap1悬空就会导致上边的这种问题。
在这里我们将ap1与ap3中的_a都进行了++操作,在编译器上它是不会提示此处有错误,并且也能成功的生成,但是如果在运行的时候这里会崩溃,访问就会出问题。
而这个问题就是因为权限的转移造成的。
这里是auto_ptr里面基本的代码,从图中也可以看出是构造,析构和auto_ptr的指针取值。
在更进一步了解auto_ptr之前我们要知道一个事情,每一种智能指针都要有几个特性。一个是RAII,另一个是像指针一样,第三个则是拷贝问题。
那么这里来解决一下auto_ptr的拷贝问题。