最近被指针折磨了一下,赶紧来学习一下智能指针,希望以后都可以用智能指针替换“裸指针”,不要再自己去释放了。

一、什么是智能指针?

在说智能指针之前,先来看看“不智能指针”,也就是“裸指针”,这里直接简称指针。

指针是源自 C 语言的概念,本质上是一个内存地址索引,代表了一小片内存区域(也可能会很大),能够直接读写内存。

因为它完全映射了计算机硬件,所以操作效率高,是 C/C++ 高效的根源。当然,这也是引起无数麻烦的根源。访问无效数据、指针越界,或者内存分配后没有及时释放,就会导致运行错误、内存泄漏、资源丢失等一系列严重的问题。

像Java、GO等语言没有这方面的顾虑,因为它们内置了一个“垃圾回收”机制,会检测不再使用的内存,自动释放资源,让程序员不必为此费心。

而C++也不是没有垃圾回收,不过不是 Java、Go 那种严格意义上的垃圾回收,而是广义上的垃圾回收,这就是构造 / 析构函数和 RAII 惯用法(Resource Acquisition Is Initialization)。

也正是这个所谓的广义垃圾回收,让我们有了一丝自动内存释放、资源回收的希望。具体做法就是,应用代理模式,将裸指针包裹起来,在构造函数里初始化,在析构函数里释放,这样当对象失效销毁时,C++ 就会自动调用析构函数,完成内存释放、资源回收等清理工作。

智能指针就是这样一个替你应用代理模式,帮你包裹指针的东西。它完全实践了 RAII,包装了裸指针,而且因为重载了 * 和 -> 操作符,用起来和原始指针一模一样。

除此之外,还综合考虑了各种现实的应用场景,能够自动适应各种复杂的情况,防止误用指针导致的隐患非常“聪明”,所以被称为“智能指针”。

常用的有两种智能指针,分别是 unique_ptrshared_ptr

二、unique_ptr

1、基本介绍

unique_ptr是最简单、最容易使用的一个智能指针,在声明的时候必须用模板参数指定类型:

unique_ptr<int> ptr1(new int(10)); // int智能指针
assert(*ptr1 == 10);               // 可以使用*取内容
assert(ptr1 != nullptr);           // 可以判断是否为空指针unique_ptr<string> ptr2(new string("hello")); // string智能指针
assert(*ptr2 == "hello");                     // 可以使用*取内容
assert(ptr2->size() == 5);                    // 可以使用->调用成员函数

就跟前面说的那样,智能指针通过构造函数和析构函数完成资源获取和释放,所以智能指针本质上是一个对象,而不是像名字那样,是一个指针。所以,不要试图对它调用delete,它会自动管理初始化时的指针,在离开作用域的时候析构释放内存。

需要注意的是,它并没有定义加减运算,不能随意移动指针地址,避免了指针越界等危险操作,可以让代码更安全。

除此之外,不同于普通对象,智能指针需要在声明的时候进行初始化,未初始化的unique_ptr表示空指针,对未初始化的unique_ptr赋值,相当于直接操作了空指针,运行时会产生致命的错误(比如core dump)。

为了避免这些低级错误,可以通过调用工厂函数make_unique(),强制创建智能指针时必须初始化。

auto ptr3 = make_unique<int>(42); // 工厂函数创建智能指针
assert(ptr3 && *ptr3 == 42);auto ptr4 = make_unique<string>("god of war"); // 工厂函数创建智能指针
assert(!ptr4->empty());

但是呢,make_unique() 要求 C++14,好在它的原理比较简单。如果你使用的是 C++11,也可以自己实现一个简化版的 make_unique(),参考如下:

template <class T, class... Args> // 可变参数模板
std::unique_ptr<T>                // 返回智能指针
my_make_unique(Args &&...args)    // 可变参数模板的入口参数
{return std::unique_ptr<T>(               // 构造智能指针new T(std::forward<Args>(args)...)); // 完美转发
}
2、unique_ptr 的所有权

使用 unique_ptr 的时候还要特别注意指针的“所有权”问题。正如它的名字,表示指针的所有权是“唯一”的,不允许共享,任何时候只能有一个“人”持有它。

为了实现这个目的,unique_ptr 应用了 C++ 的“转移”(move)语义,同时禁止了拷贝赋值,所以,在向另一个 unique_ptr 赋值的时候,要特别留意,必须用 std::move() 函数显式地声明所有权转移。

赋值操作之后,指针的所有权被转走了,原来的unique_ptr变成了空指针,新的unique_ptr接替了管理权,保证所有权的唯一性:

auto ptr1 = make_unique<int>(42); // 工厂函数创建智能指针
assert(ptr1 && *ptr1 == 42);      // 此时智能指针有效auto ptr2 = std::move(ptr1); // 使用move()转移所有权
assert(!ptr1 && ptr2);       // ptr1变成了空指针

三、shared_ptr

1、基本介绍

shared_ptr,它是一个比 unique_ptr 更“智能”的智能指针。和unique的用法几乎一样,代码如下:

shared_ptr<int> ptr1(new int(10)); // int智能指针
assert(*ptr1 == 10);               // 可以使用*取内容shared_ptr<string> ptr2(new string("hello")); // string智能指针
assert(*ptr2 == "hello");                     // 可以使用*取内容auto ptr3 = make_shared<int>(42); // 工厂函数创建智能指针
assert(ptr3 && *ptr3 == 42);      // 可以判断是否为空指针auto ptr4 = make_shared<string>("zelda"); // 工厂函数创建智能指针
assert(!ptr4->empty());                   // 可以使用->调用成员函数

但 shared_ptr 的名字明显表示了它与 unique_ptr 的最大不同点:它的所有权是可以被安全共享的,也就是说支持拷贝赋值,允许被多个“人”同时持有,就像原始指针一样。


auto ptr1 = make_shared<int>(42);   // 工厂函数创建智能指针
assert(ptr1 && ptr1.unique() );     // 此时智能指针有效且唯一auto ptr2 = ptr1;                    // 直接拷贝赋值,不需要使用move()
assert(ptr1 && ptr2);               // 此时两个智能指针均有效assert(ptr1 == ptr2);               // shared_ptr可以直接比较// 两个智能指针均不唯一,且引用计数为2
assert(!ptr1.unique() && ptr1.use_count() == 2);
assert(!ptr2.unique() && ptr2.use_count() == 2);

shared_ptr 支持安全共享的秘密在于内部使用了“引用计数”。

引用计数最开始的时候是 1,表示只有一个持有者。如果发生拷贝赋值——也就是共享的时候,引用计数就增加,而发生析构销毁的时候,引用计数就减少。只有当引用计数减少到 0,也就是说,没有任何人使用这个指针的时候,它才会真正调用 delete 释放内存。

因为 shared_ptr 具有完整的“值语义”(即可以拷贝赋值),所以,**它可以在任何场合替代原始指针,而不用再担心资源回收的问题,**比如用于容器存储指针、用于函数安全返回动态创建的对象,等等。

2、shared_ptr 的注意事项

虽然 shared_ptr 非常“智能”,但天下没有免费的午餐,它也是有代价的,引用计数的存储和管理都是成本,这方面是 shared_ptr 不如 unique_ptr 的地方。

如果不考虑应用场合,过度使用 shared_ptr 就会降低运行效率。不过,你也不需要太担心,shared_ptr 内部有很好的优化,在非极端情况下,它的开销都很小。

第二个要注意的地方是 shared_ptr 的销毁动作。

你要特别小心对象的析构函数,不要有非常复杂、严重阻塞的操作。一旦 shared_ptr 在某个不确定时间点析构释放资源,就会阻塞整个进程或者线程,“整个世界都会静止不动”


class DemoShared final      //  危险的类,不定时的地雷
{
public:DemoShared() = default;~DemoShared()            // 复杂的操作会导致shared_ptr析构时世界静止{// Stop The World ...}
};

最后,就是“循环引用”的问题,这在把 shared_ptr 作为类成员的时候最容易出现,典型的例子就是链表节点。

class Node final
{
public:using this_type     = Node;using shared_type   = std::shared_ptr<this_type>;
public:shared_type     next;            // 使用智能指针来指向下一个节点
};auto n1 = make_shared<Node>();     // 工厂函数创建智能指针
auto n2 = make_shared<Node>();       // 工厂函数创建智能指针assert(n1.use_count() == 1);     // 引用计数为1
assert(n2.use_count() == 1);n1->next = n2;                        // 两个节点互指,形成了循环引用
n2->next = n1;assert(n1.use_count() == 2);        // 引用计数为2
assert(n2.use_count() == 2);      // 无法减到0,无法销毁,导致内存泄漏

在这里,两个节点指针刚创建时,引用计数是 1,但指针互指(即拷贝赋值)之后,引用计数都变成了 2。

这个时候,shared_ptr 就“犯傻”了,意识不到这是一个循环引用,多算了一次计数,后果就是引用计数无法减到 0,无法调用析构函数执行 delete,最终导致内存泄漏。

可能上面这个例子,很容易看出来,但在实际开发中,很难肉眼看到,,这时候就需要weak_ptr这个帮手了。

weak_ptr 顾名思义,功能很“弱”。它专门为打破循环引用而设计,只观察指针,不会增加引用计数(弱引用),但在需要的时候,可以调用成员函数 lock(),获取 shared_ptr(强引用)。

刚才的例子里,只要你改用 weak_ptr,循环引用的烦恼就会烟消云散:


class Node final
{
public:using this_type     = Node;// 注意这里,别名改用weak_ptrusing shared_type   = std::weak_ptr<this_type>;
public:shared_type     next;    // 因为用了别名,所以代码不需要改动
};auto n1 = make_shared<Node>();  // 工厂函数创建智能指针
auto n2 = make_shared<Node>();  // 工厂函数创建智能指针n1->next = n2;             // 两个节点互指,形成了循环引用
n2->next = n1;assert(n1.use_count() == 1);    // 因为使用了weak_ptr,引用计数为1
assert(n2.use_count() == 1);   // 打破循环引用,不会导致内存泄漏if (!n1->next.expired()) {     // 检查指针是否有效auto ptr = n1->next.lock();  // lock()获取shared_ptrassert(ptr == n2);
}

四、总结

主要知识点:

  • 智能指针是代理模式的具体应用,它使用 RAII 技术代理了裸指针,能够自动释放内存,无需程序员干预,所以被称为“智能指针”。
  • 如果指针是“独占”使用,就应该选择 unique_ptr,它为裸指针添加了很多限制,更加安全。
  • 如果指针是“共享”使用,就应该选择 shared_ptr,它的功能非常完善,用法几乎与原始指针一样。
  • 应当使用工厂函数 make_unique()、make_shared() 来创建智能指针,强制初始化,而且还能使用 auto 来简化声明。
  • shared_ptr 有少量的管理成本,也会引发一些难以排查的错误,所以不要过度使用。

【C++】初识智能指针:智能在哪?相关推荐

  1. android ndk 智能指针,智能指针与弱引用详解

    在android 中可以广泛看到的template class Sp 句柄类实际上是android 为实现垃圾回收机制的智能指针.智能指针是c++ 中的一个概念,因为c++ 本身不具备垃圾回收机制,而 ...

  2. 五点讲述C++智能指针的点点滴滴

    (在学习C/C++或者想要学习C/C++可以加我们的学习交流QQ群:712263501群内有相关学习资料) 0.摘要 本文先讲了智能指针存在之前C++面临的窘境,并顺理成章地引出利用RAII技术封装普 ...

  3. 【Smart_Point】C/C++ 中智能指针

    C++11智能指针 目录 C++11智能指针 1.1 C++11智能指针介绍 1.2 为什么要使用智能指针 1.2.1 auto_ptr(C++98的方案,C++11已经抛弃)采用所有权模式. 1.2 ...

  4. C++——智能指针——auto_ptr、shared_ptr、unique_ptr

    1.4.智能指针 智能指针是行为类似于指针的类对象. C++11 中提供了三种智能指针,使用这些智能指针时需要引用头文件 : ·shared_ptr; ·unique_ptr; ·auto_ptr; ...

  5. VTK修炼之道80:VTK开发基础_智能指针与引用计数

    1.引用计数 VTK经过多年的开发与维护,已经形成了一套稳定的框架和开发规则.因此,了解这些规则和框架是定制VTK类的基础,这其中用到了大量面向对象的设计模式,例如对象工程模式.观察者/命令模式:还有 ...

  6. C++11智能指针shared_ptr、weak_ptr、unique_ptr用法

    该博文为原创文章,未经博主同意不得转载,如同意转载请注明博文出处 本文章博客地址:https://cplusplus.blog.csdn.net/article/details/105065859 智 ...

  7. 【C++】智能指针 Smart Pointer

    智能指针 智能指针 Smart Pointer auto_ptr 智能指针的自实现 shared_ptr weak_ptr unique_ptr 智能指针 Smart Pointer 用来改善传统指针 ...

  8. 详解C++11智能指针

    详解C++11智能指针 前言 C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用 ...

  9. 智能指针 shared_ptr 解析

    近期正在进行<Effective C++>的第二遍阅读,书里面多个条款涉及到了shared_ptr智能指针,介绍的太分散,学习起来麻烦.写篇blog整理一下. LinJM   @HQU s ...

  10. C++ 智能指针 :内存泄漏、 RAII、智能指针、auto_ptr、unique_ptr、shared_ptr、weak_ptr、定制删除器deleter

    文章目录 内存泄漏 什么是内存泄漏 内存泄漏的危害: 如何避免内存泄漏 RAII 智能指针 auto_ptr unique_ptr shared_ptr 循环引用问题 weak_ptr 定制删除器 内 ...

最新文章

  1. 同步服务器文档并打印,如何打印远程服务器文件内容
  2. python【力扣LeetCode算法题库】8-字符串转换整数 (atoi)
  3. Java--23种设计模式之decorator模式
  4. jdk-12.0.2 64位最新版
  5. HBase timestamp(version)
  6. .Net单元测试方法
  7. 【CodeForces - 278C 】Learning Languages(并查集,思维)
  8. 赵明晒荣耀20青春版三色真机图:哪款会是你的菜?
  9. Android第三十三期 - Dialog的应用
  10. 两列数据对比找不同,并且高亮数据不同的单元格
  11. 【python】实现将json字幕转srt,并将繁体中文翻译为简体中文
  12. android跑马灯监听,TextView跑马灯状态监听
  13. 交响曲、协奏曲、奏鸣曲
  14. elasticsearch Routing 路由详解
  15. 国外问卷调查工具操作教程
  16. 特斯拉、软件与颠覆,下一家震惊世界的科技公司会是谁?
  17. 熔断器Hystrix作用
  18. scala akka 修炼之路6(scala函数式柯里化风格应用场景分析)
  19. 2020.1.14课程摘要(逻辑教育-王劲胜)
  20. 互联网低成本的创业与变现

热门文章

  1. BUUctf [GXYCTF2019]Ping Ping Ping
  2. 视频教程-shader 基础之 2D技巧集合-Unity3D
  3. 计算单目标跟踪器的平均CLE
  4. 苹果Mac 无法读写NTFS格式的U盘或移动硬盘?一次解决
  5. python学习之旅_第1天
  6. 华为P9黑屏的解决方案-更换屏幕
  7. css filter:inver属性灵活使用
  8. B 站神曲 damedane:精髓在于深度变换,五分钟就能学会
  9. 【魔店】拼多多店群玩法是什么?如何运营店铺?
  10. 2个阶乘什么意思_两个阶乘符号连在一起是什么意思