大家好,欢迎来到IT知识分享网。
“指针问题”
C语言中的指针语法是很多初学者的噩梦,但是由于指针能够便捷的管理内存,后进者C++并没有抛弃指针语法。不过,由于指针过于灵活,很容易为程序带来内存溢出、内存泄漏等问题,即使是经验老道的程序员也不敢说能够完全避免这些问题,所以C++提供了引用语法,以期解决指针带来的问题。
可惜的是,近些年的实践证明C++是离不开指针(这一点我们以后再谈)的,因此早期C语言程序员使用指针面临的问题,C++程序员也不得不考虑。C++的语法比C语言的语法复杂得多,若是不能提供一种缓解“指针问题”的方案,那真的有些说不过去了,所以“智能指针”就被设计出来了。
本文将讨论C++11中的std::shared_ptr智能指针。
C++中的指针的一个典型用法就是管理一段内存。常规做法是通过类似于 malloc() 的内存管理函数,或者new关键字等方法分配一段内存,并且定义一个指针指向这块内存,之后便可通过指针访问这块内存。
不过一般来说,malloc() 或者new关键字分配的内存不会被系统自动回收,这意味着即使分配的内存不再被使用,但若是程序员不主动释放分配的内存,这块内存将永远不能再被别的逻辑使用,这就是所谓的“内存泄漏”。
可能有读者会想,保证 new/malloc() 和 delete/free() 的配对使用不就好了吗?这有什么难的!的确,在简单的项目里比较容易保证C++程序不会发生“内存泄漏”,但若是项目稍稍复杂一些,可能要使用别人提供的接口函数,这时再去确定内存的生命周期就稍显麻烦了,特别是有的同事懒得写文档,而他的接口函数源代码不可得的情况下。
简而言之,C++中类C语言的普通指针管理内存的确有着不方便的地方——程序员必须非常清楚整个架构,才能知道某段已分配的内存是否仍在被使用中,进而确保安全的释放已经不再使用的内存,这并非易事。
std::shared_ptr<> 是什么?
std::shared_ptr<>是C++11标准中的智能指针类,它很聪明,能够知道自己管理的对象是否还有人使用,若是没有人再使用自己管理的对象,就会自动的删除该对象。所以,shared_ptr能够在最大程度上帮助C++程序员避免内存泄漏问题,也能够避免“悬空指针”的出现。
正如shared_ptr的字面含义,“shared”意味着共享,即不同的shared_ptr可以与同一个指针建立联系,其内部通过“引用计数机制”实现自动管理指针。
通常来说,每一个shared_ptr对象在其内部都管理两部分内容:
- shared_ptr 对象本身
- shared_ptr 对象管理的内容
“引用计数机制”
假设计划使用指针 p 指向一块分配的内存,现在使用C++的智能指针类 shared_ptr 自动管理这块内存。
- 当 shared_ptr 类实例化一个对象与指针 p 绑定时,其内部的构造函数会将对应指针 p 的计数加 1。
- 当 shared_ptr 对象完成自己的生命周期,它的析构函数会将指针 p 对应的计数减 1。
引用计数减少到 0,就意味着没有 shared_ptr 对象还与指针 p 管理的内存绑定,也即没有人再使用这块内存了,于是 shared_ptr 的析构函数调用“delete”方法释放这块内存。
创建一个shared_ptr对象
将一个裸指针绑定到 shared_ptr 对象上是简单的,例如:
std::shared_ptr<int> p1(new int());
上面这行C++代码在堆上分配了两块内存,一块用于存储new出来的 int 值,一块用于存储 shared_ptr 对象本身。正如前面讨论的,shared_ptr 对象在其内部使用“引用计数”机制管理“new int()”,这里“计数”的初始值显然是 1,因为暂时只有一个 shared_ptr 对象指向“new int()”上。
C++智能指针类 shared_ptr 提供了成员函数 use_count() 用于检查实际的“计数值”,请参考稍后的实例。
怎样将指针“赋值”给shared_ptr?
因为 shared_ptr 类的构造函数是 Explicit 的,所以像下面这样的C++代码是非法的:
// 非法 std::shared_ptr<int> p1 = new int();
不过,除了前文提到的那样通过shared_ptr构造函数绑定指针,还有一种推荐的方法用于绑定指针:
std::shared_ptr<int> p1 = std::make_shared<int>();
std::make_shared执行了类似于 shared_ptr 构造函数类似的工作:在堆上分配两块内存,一块用于存储 int 值,一块用于存储 shared_ptr 对象本身。
“解绑”
现在我们知道了怎样将普通的裸指针与 shared_ptr 对象绑定,那么怎样才能“解绑”呢?使用 shared_ptr 类提供的 reset() 成员函数即可:
p1.reset();
reset() 函数会将绑定指针的计数减 1,如果计数减小到 0,那么它将删除绑定的指针。reset() 函数也可以接收一个参数,例如:
p1.reset(new int(32));
这种情况下,它将与一个新指针“new int(32)”绑定,因此内部的计数将重新变为 1。
如果想直接解绑 p1 绑定的指针,还可以使用 nullptr,例如:
p1 = nullptr;
shared_ptr 并不是严格意义的“指针”
C++中的 shared_ptr 对象在某种程度上虽然表现的很像传统C语言中的指针,例如可以使用 *,->运算符,但是它并不是严格意义上的“指针”,这一点应该始终明白。
实例
下面将使用一个较为完整的智能指针类 shared_ptr 使用实例结束本文:
#include <iostream> #include <memory> // 使用shared_ptr需要包含此头文件 int main() { // 使用make_shared创建一个shared_ptr std::shared_ptr<int> p1 = std::make_shared<int>(); *p1 = 78; std::cout << "p1 = " << *p1 << std::endl; // 打印当前引用计数 std::cout << "p1 Reference count = " << p1.use_count() << std::endl; // 第二个shared_ptr对象绑定到p1 // 引用计数将会加 1 std::shared_ptr<int> p2(p1); // 打印当前引用计数 std::cout << "p2 Reference count = " << p2.use_count() << std::endl; std::cout << "p1 Reference count = " << p1.use_count() << std::endl; // 对比智能指针 if (p1 == p2) { std::cout << "p1 and p2 are pointing to same pointer\n"; } std::cout<<"Reset p1 "<<std::endl; p1.reset(); // 调用reset(),p1将与指针解绑 // 所以 p1 的引用计数将为 0 std::cout << "p1 Reference Count = " << p1.use_count() << std::endl; // 调用带参数的reset() // p1与新指针绑定,引用计数变为 1 p1.reset(new int(11)); std::cout << "p1 Reference Count = " << p1.use_count() << std::endl; // 通过 nullptr 清空 p1 p1 = nullptr; std::cout << "p1 Reference Count = " << p1.use_count() << std::endl; if (!p1) { std::cout << "p1 is NULL" << std::endl; } return 0; }
编译这段C++代码时,记得添加C++11标准选项,最终得到如下输出:
# g++ t.cpp -std=c++11 # ./a.out p1 = 78 p1 Reference count = 1 p2 Reference count = 2 p1 Reference count = 2 p1 and p2 are pointing to same pointer Reset p1 p1 Reference Count = 0 p1 Reference Count = 1 p1 Reference Count = 0 p1 is
欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦。
未经许可,禁止转载。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/161977.html