| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> ?关于C++之智能指针 -> 正文阅读 |
|
[C++知识库]?关于C++之智能指针 |
首先,智能指针是模板类。智能指针是行为类似于指针的类对象,但这种对象还有其他功能。
当remodel()这样的函数终止(不管是正常终止,还是由于出现了异常而终止),本地变量都将从栈内存中删除——因此指针ps占据的内存将被释放。如果ps指向的内存也被释放,那该有多好啊。如果ps有一个析构函数,该析构函数将在ps过期时释放它指向的内存。因此,ps的问题在于,它只是一个常规指针,不是有析构函数的类对象。如果它是对象,则可以在对象过期时,让它的析构函数删除指向的内存。这正是auto_ptr、unique_ptr和shared_ptr背后的思想。模板auto_ptr是C++98提供的解决方案,C++11已将其摒弃,并提供了另外两种解决方案。然而,虽然auto_ptr被摒弃,但它已使用了多年;同时,如果你的编译器不支持其他两种解决方案,auto_ptr将是唯一的选择。 这三个智能指针模板(auto_ptr、unique_ptr和shared_ptr)都定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给这种对象。当智能指针过期时,其析构函数将使用delete来释放内存。因此,如果将new返回的地址赋给这些对象,将无需记住稍后释放这些内存:在智能指针过期时,这些内存将自动被释放。 下图说明了auto_ptr和常规指针在行为方面的差别;share_ptr和unique_ptr的行为与auto_ptr相同。 要创建智能指针对象,必须包含头文件memory,该文件模板定义。然后使用通常的模板语法来实例化所需类型的指针。例如,模板auto_ptr包含如下构造函数:
throw()意味着构造函数不会引发异常;与auto_ptr一样,throw()也被摒弃。因此,请求X类型的auto_ptr将获得一个指向X类型的auto_ptr:
new double是new返回的指针,指向新分配的内存块。它是构造函数auto_ptr<double>的参数,即对应于原型中形参p的实参。同样,new string也是构造函数的实参。其他两种智能指针使用同样的语法:
因此,要转换前面的remodel()函数,应按下面3个步骤进行: 1.包含头文件<memory>;2.将指向string的指针替换为指向string的智能指针对象;3.删除delete语句。 下面是使用auto_ptr修改该函数的结果:
下面程序清单是一个简单的程序,演示了如何使用全部三种智能指针。要编译该程序,编译器必须支持C++11新增的类share_ptr和unique_ptr。每个智能指针都放在一个代码块内,这样离开代码块时,指针将过期。
所有智能指针类都有一个explicit构造函数,该构造函数将指针作为参数。因此不能隐式自动将普通指针转换为智能指针对象:
由于智能指针模板类的定义方式,智能指针对象的很多方面都类似于常规指针。例如,如果ps是一个智能指针对象,则可以对它执行解除引用操作(* ps)、用它来访问结构成员(ps->puffIndex)、将它赋给指向相同类型的常规指针。还可以将智能指针对象赋给另一个同类型的智能指针对象,但将引起一个问题,之后会讨论到。 在此之前,先说说对全部三种智能指针都应避免的一点:
pvac过期时,程序将把delete运算符用于非堆内存,这是错误的。 有关智能指针的注意事项:为何有三种智能指针呢?实际上有4种,但这里不讨论weak_ptr。为何摒弃auto_ptr呢? 先来看下面的赋值语句:
如果ps和vocation是常规指针,则两个指针将指向同一个string对象。这是不能接受的,因为程序将试图删除同一个对象两次——一次是ps过期时,另一次是vocation过期时。要避免这种问题,方法有多种:
当然,同样的策略也适用于复制构造函数。 一个不合适使用auto_ptr的示例:
pwin = films[2];?将所有权从films[2]转让给了pwin,这导致films[2]不再引用该字符串。在auto_ptr放弃对象的所有权后,便可能使用它来访问该对象。当程序打印films[2]指向的字符串时,却发现这是一个空指针。 如果在程序清单中使用shared_ptr代替auto_ptr(这要求编译器支持C++11新增的shared_ptr类),则程序将正常运行,正常输出。 差别在于:
这次pwin和films[2]指向同一个对象,而引用计数从1增加到2。在程序末尾,后声明的pwin首先调用其析构函数,该析构函数将引用计数降低到1。然后,shared_ptr数组的成员被释放,对filmsp[2]调用析构函数时,将引用计数降低到0,并释放以前分配的空间。 因此使用shared_ptr时,程序清单运行正常;而使用auto_ptr时,该程序在运行阶段崩溃。如果使用unique_ptr,结果将如何呢?与auto_ptr一样,unique_ptr也采用所有权模型。但使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译器因这行代码出现错误:pwin = films[2]; unique_ptr为何优于auto_ptr?
在语句#3中,p2接管string对象的所有权后,p1的所有权将被剥夺。前面说过,这是件好事,可防止p1和p2的析构函数试图删除同一个对象;但如果程序随后试图使用p1,这将是件坏事,因为p1不再指向有效的数据。 下面来看使用unique_ptr的情况:
编译器认为语句#6非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全(编译阶段错误比潜在的程序崩溃更安全)。 但有时候,将一个智能指针赋给另一个并不会留下危险的悬挂指针。假设有如下函数定义并调用:
demo()返回一个临时unique_ptr,然后ps接管了原本归返回的unique_ptr所有的对象,而返回的unique_ptr被销毁。这没有问题,因为ps拥有了string对象的所有权。但这里的另一个好处是,demo()返回的临时unique_ptr很快被销毁,没有机会使用它来访问无效的数据。换句话说,没有理由禁止这种赋值。神奇的是,编译器确实允许这种赋值! 总之,程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器将禁止这样做:
语句#1将留下悬挂的unique_ptr(pu1),这可能导致危害。语句#2不会留下悬挂的unique_ptr,因为它调用unique_ptr的构造函数,该构造函数创建的临时对象在其所有权转让给pu后就会被销毁。这种随情况而异的行为表明,unique_ptr优于允许两种赋值的auto_ptr。这也是禁止(只是一种建议,编译器并不禁止)在容器对象中使用auto_ptr,但允许使用unique_ptr的原因。如果容器算法试图对包含unique_ptr的容器执行类似于语句#1的操作,将导致编译错误;如果算法试图执行类似于语句#2的操作,则不会有任何问题。而对于auto_ptr,类似于语句#1的操作可能导致不确定的行为和神秘的崩溃。 当然,你可能确实想执行类似于语句#1的操作。仅当以非智能的方式使用遗弃的智能指针(如解除引用时),这种赋值才不安全。要安全地重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。下面是一个使用前述demo()函数的例子,该函数返回一个unique_ptr<string>对象:
你可能会问,unique_ptr如何能够区分安全和不安全的用法呢?答案是它使用了C++11新增的移动构造函数和右值引用。
相比于auto_ptr,unique_ptr还有另一个优点。它有一个可用于数组的变体。别忘了,必须将delete和new配对,将delete []和new []配对。模板auto_ptr使用delete而不是delete [ ],因此只能与new一起使用,而不能与new []一起使用。但unique_ptr有使用new []和delete []的版本:
智能指针的选择:应使用哪种智能指针呢?如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:有一个指针数组,并使用一些辅助指针来标识特定的元素,如最大的元素和最小的元素;两个对象包含都指向第三个对象的指针;STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出警告)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。 如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权将转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器中,只要不调用将一个unique_ptr复制或赋给另一个的方法或算法(如sort())。例如,可在程序中使用类似于下面的代码段,这里假设程序包含正确的include和using语句:
其中的push_back()调用没有问题,因为它返回一个临时unique_ptr,该unique_ptr被赋给vp中的一个unique_ptr。另外,如果按值而不是按引用给show()传递对象,for_each()语句将非法,因为这将导致使用一个来自vp的非临时unique_ptr初始化pi,而这是不允许的。前面说过,编译器将发现错误使用unique_ptr的企图。 在unique_ptr为右值时,可将其赋给shared_ptr,这与将一个unique_ptr赋给另一个需要满足的条件相同。与前面一样,在下面的代码中,make_int()的返回类型为unique_ptr<int>:
#2:No matching constructor for initialization of 'shared_ptr<int>' 以下是shared_ptr所有的构造函数: 模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr。shared_ptr将接管原来归unique_ptr所有的对象。 在满足unique_ptr要求的条件时,也可使用auto_ptr,但unique_ptr是更好的选择。如果你的编译器没有提供unique_ptr,可考虑使用BOOST库提供的scoped_ptr,它与unique_ptr类似。 |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 2:43:36- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |