点击并拖拽以移动

点击并拖拽以移动

前言

C中我们会进行malloc一块内存,然后free掉。但是经常会遇到我们忘记写free,导致内存溢出,C++也有类似的情况,为了解决掉我们忘记释放内存的习惯,C++引入了几种智能指针,为的就是让函数可以在正常终止或者异常终止的情况下,改指针的指向的内存都可以处于正确的状态。shared_ptr、unique_ptr、weak_ptr、auto_ptr。其中shared_ptr允许多个指针指向同一个对象;unique_ptr则"独占"所指向的对象, 保证同一时间只有一个 指针指向某个内存,对于避免内存泄漏很有用;weak_ptr则是一种弱引用,指向shared_ptr所管理的对象;而auto_ptr则是类似于unique_ptr的一种指针,并且已经在C++11摒弃了它


作者:良知犹存

转载授权以及围观:欢迎添加微信公众号:羽林君


点击并拖拽以移动

shared_ptr

类似vector,智能指针也是模板。因此当我们创建一个指针指针的时,必须提供额外的信息——指针可以指向的类型。默认初始化的智能指针中保存着一个空指针。

点击并拖拽以移动

智能指针使用和普通指针类似。解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果就是检测它是否为空。

shared_ptr<T> p; //空智能指针 可以指向类型为T的对象
p.get(); //返回p中保存的指针
swap(p,q);//交换p和q的指针
p.swap(q);//交换p和q的指针
shared_ptr<T> p(q); //p是share_ptr q的拷贝;此操作会递增q中的计数器。此外q中的指针必须能转为T*;
p.use_count(); //返回与p共享对象的智能指针的数量,可能很慢,主要用于调试

点击并拖拽以移动

程序使用动态内存出于以下三种原因之一 1、程序不知道自己需要多少对象; 2、程序不知道所需对象的准确类型;

3、程序需要在多个对象间共享数据

shared_ptr可以指向特定类型的对象,用于自动释放所指的对象,所以我们会经常多的用,此外还有一个最安全的分配和使用动态内存的方法就是调用一个名为make_shared的标准库函数;和make_pair、make_unique一样,都是在原有基础进行的泛化。

make_shared的用法

make_shared 在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr,与智能指针一样,make_shared也定义在头文件memory中;当要用make_shared时,必须指定想要创建的对象类型,定义方式与模板类相同,在函数名之后跟一个尖括号,在其中给出类型;

点击并拖拽以移动

点击并拖拽以移动

unique_ptr

unique_ptr<>是c ++ 11提供的智能指针实现之一,用于防止内存泄漏。unique_ptr对象包含一个原始指针,并负责其生命周期。当这个对象被销毁的时候,它的析构函数会删除关联的原始指针。unique_ptr有重载的- >和*运算符,所以它可以被用于类似于普通的指针。

unique_ptr<T> u1;//空unique_ptr,可以指向类型为T的对象。
unique_ptr<T ,D> u2;


//u1会使用delet来释放它的指针,u2会使用一个类型为D的可调用对象来释放它的指针
unique_ptr<T ,D> u(d);///空unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete
u1 = nullptr; //释放u指向的对象,将u置为空


u.release(); //u放弃对指针的控制权,返回指针,并将u置为空
u.reset(); //释放u指向的对象
u.reset(q); //如果提供了内置指针q,令u指向这个对象;否则将u置空
u.reset(nullptr);

点击并拖拽以移动

虽然我们不能拷贝或赋值unique_ptr,但是可以通过release或reset将指针的所有权从一个(非const)unique_ptr转移到另一个unique_ptr;

点击并拖拽以移动

C++11加入了make_shared,C++14加入了make_unique,如果你处于C++11环境也不必担心,因为make_unique很容易实现,make_unique只是将其参数完美转发给待创建对象的构造函数,并返回一个由原始指针初始化得到的unique_ptr。这种实现形式并不支持数组或自定义删除器,但它至少表明实现make_unique并不困难。

点击并拖拽以移动

C++ 14引入了std::make_unique,因为由于未指定参数评估顺序,因此这是不安全的:

f(std::unique_ptr<MyClass>(new MyClass(param)), g()); // Syntax A

点击并拖拽以移动

(说明:如果评估首先为原始指针分配内存,然后调用g(),并且在std::unique_ptr构造之前引发了异常,则内存被泄漏。)

调用std::make_unique是一种限制调用顺序的方法,从而使事情变得安全:

f(std::make_unique<MyClass>(param), g());             // Syntax B

点击并拖拽以移动

至于更加详细的解释大家可以网上搜索两者的对比。

点击并拖拽以移动

weak_ptr

weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

class A;
class B;
class A {
public:
        A() {cout<< "A Created" << endl;}
        ~A() {   cout<< "A Destroyed" << endl;
}
        shared_ptr<B>ptr;
};
class B {
public:
        B() {cout<< "B Created" << endl;       }
        ~B() {cout<< "B Destroyed" << endl;}
        shared_ptr<A>ptr;
};

点击并拖拽以移动

int main() {
        shared_ptr<A>pt1(new A());
        shared_ptr<B>pt2(new B());
        pt1->ptr = pt2;
   pt2->ptr = pt1;
   cout << "useof pt1: " << pt1.use_count() << endl;
   cout << "useof pt2: " << pt2.use_count() << endl;
   return 0;
}

点击并拖拽以移动

点击并拖拽以移动

weak_ptr是一种不控制对象生命周期的智能指针,它与一个 shared_ptr 绑定,却不参与引用计数。一旦最后一个shared_ptr 销毁,对象就会释放。

weak_ptr 作用是在需要的时候变出一个 shared_ptr,其他时候不干扰 shared_ptr 的引用计数。weak_ptr没有 * 和 -> 符号,需要用lock() 获得shared_ptr ,进而使用对象。

利用 weak_ptr可以消除上面的循环引用,例子如下。

class A;
class B;
class A {
public:
        A() {cout<< "A Created" << endl;}
        ~A() {   cout<< "A Destroyed" << endl;
}
        weak_ptr<B>ptr;
};
class B {
public:
        B() {cout<< "B Created" << endl;       }
        ~B() {cout<< "B Destroyed" << endl;}
        weak_ptr<A>ptr;
};

点击并拖拽以移动

int main() {
        shared_ptr<A>pt1(new A());
        shared_ptr<B>pt2(new B());
        pt1->ptr = pt2;
   pt2->ptr = pt1;
   cout << "useof pt1: " << pt1.use_count() << endl;
   cout << "useof pt2: " << pt2.use_count() << endl;
   return 0;
}

点击并拖拽以移动

点击并拖拽以移动

点击并拖拽以移动

auto_ptr

auto_ptr< string> ps (new string ("I reigned lonely as a cloud.”);
auto_ptr<string> vocation;
vocaticn = ps;

点击并拖拽以移动

     上述赋值语句将完成什么工作呢?如果ps和vocation是常规指针,则两个指针将指向同一个string对象。这是不能接受的,因为程序将试图删除同一个对象两次——一次是ps过期时,另一次是vocation过期时。要避免这种问题,方法有多种:

1.定义陚值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本,缺点是浪费空间,所以智能指针都未采用此方案。
2.建立所有权(ownership)概念。对于特定的对象,只能有一个智能指针可拥有,这样只有拥有对象的智能指针的构造函数会删除该对象。然后让赋值操作转让所有权。这就是用于auto_ptr和uniqiie_ptr 的策略,但unique_ptr的策略更严格。
3.创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加1,而指针过期时,计数将减1,。当减为0时才调用delete。这是shared_ptr采用的策略。
当然,同样的策略也适用于复制构造函数。
每种方法都有其用途,但为何说要摒弃auto_ptr呢?
下面举个例子来说明。

点击并拖拽以移动

#include <iostream>
#include <string>
#include <memory>
using namespace std;


int main() {
auto_ptr<string> films[5] =
{
auto_ptr<string> (new string("Fowl Balls")),
auto_ptr<string> (new string("Duck Walks")),
auto_ptr<string> (new string("Chicken Runs")),
auto_ptr<string> (new string("Turkey Errors")),
auto_ptr<string> (new string("Goose Eggs"))
};
auto_ptr<string> pwin;
pwin = films[2]; // films[2] loses ownership. 将所有权从films[2]转让给pwin,此时films[2]不再引用该字符串从而变成空指针


cout << "The nominees for best avian baseballl film are\n";
for(int i = 0; i < 5; ++i)
cout << *films[i] << endl;
cout << "The winner is " << *pwin << endl;
cin.get();


return 0;
}

点击并拖拽以移动

运行下发现程序崩溃了,原因在上面注释已经说的很清楚,films[2]已经是空指针了,下面输出访问空指针当然会崩溃了。但这里如果把auto_ptr换成shared_ptr或unique_ptr后,程序就不会崩溃,原因如下:

2.使用shared_ptr时运行正常,因为shared_ptr采用引用计数,pwin和films[2]都指向同一块内存,在释放空间时因为事先要判断引用计数值的大小因此不会出现多次删除一个对象的错误。

1.使用unique_ptr时编译出错,与auto_ptr一样,unique_ptr也采用所有权模型,但在使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译器因下述代码行出现错误:

unique_ptr<string> pwin;
pwin = films[2]; // films[2] loses ownership.

这就是为何要摒弃auto_ptr的原因,一句话总结就是:避免潜在的内存崩溃问题。

点击并拖拽以移动

如何选择智能指针呢?

在掌握了这几种智能指针后,大家可能会想另一个问题:在实际应用中,应使用哪种智能指针呢?

下面给出几个使用指南。

(1)如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:

  • 有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;

  • 两个对象包含都指向第三个对象的指针;

  • STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。

(2)如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())。例如,可在程序中使用类似于下面的代码段。

unique_ptr<int> make_int(int n)
{
   return unique_ptr<int>(new int(n));
}
void show(unique_ptr<int> &p1)
{
   cout << *a << ' ';
}
int main()
{
  ...
   vector<unique_ptr<int> > vp(size);
   for(int i = 0; i < vp.size(); i++)
       vp[i] = make_int(rand() % 1000);              // copy temporary unique_ptr
   vp.push_back(make_int(rand() % 1000));     // ok because arg is temporary
   for_each(vp.begin(), vp.end(), show);           // use for_each()
  ...
}

点击并拖拽以移动

其中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>:

unique_ptr<int> pup(make_int(rand() % 1000));   // ok
shared_ptr<int> spp(pup);                       // not allowed, pup as lvalue
shared_ptr<int> spr(make_int(rand() % 1000));   // ok

点击并拖拽以移动

模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr。shared_ptr将接管原来归unique_ptr所有的对象。

在满足unique_ptr要求的条件时,也可使用auto_ptr,但unique_ptr是更好的选择。如果你的编译器没有unique_ptr,可考虑使用Boost库提供的scoped_ptr,它与unique_ptr类似。

这就是我分享的c++的智能指针,其中参考了好多人的文字,此外如果大家有什么更好的思路,也欢迎分享交流哈。

*—**END*—

推荐阅读

【1】c++nullptr(空指针常量)、constexpr(常量表达式)

【2】嵌入式底层开发的软件框架简述

【3】CPU中的程序是怎么运行起来的 必读

【4】C++的匿名函数(lambda表达式)

【5】阶段性文章总结分析

本公众号全部原创干货已整理成一个目录,回复[ 资源 ]即可获得。

点击并拖拽以移动

更多分享,扫码关注我

C++的智能指针你了解吗?的更多相关文章

  1. enote笔记法使用范例(2)——指针(1)智能指针

    要知道什么是智能指针,首先了解什么称为 “资源分配即初始化” what RAII:RAII—Resource Acquisition Is Initialization,即“资源分配即初始化” 在&l ...

  2. C++11 shared_ptr 智能指针 的使用,避免内存泄露

    多线程程序经常会遇到在某个线程A创建了一个对象,这个对象需要在线程B使用, 在没有shared_ptr时,因为线程A,B结束时间不确定,即在A或B线程先释放这个对象都有可能造成另一个线程崩溃, 所以为 ...

  3. C++智能指针

    引用计数技术及智能指针的简单实现 基础对象类 class Point { public: Point(int xVal = 0, int yVal = 0) : x(xVal), y(yVal) { ...

  4. EC笔记:第三部分:17、使用独立的语句将newed对象放入智能指针

    一般的智能指针都是通过一个普通指针来初始化,所以很容易写出以下的代码: #include <iostream> using namespace std; int func1(){ //返回 ...

  5. 智能指针shared_ptr的用法

    为了解决C++内存泄漏的问题,C++11引入了智能指针(Smart Pointer). 智能指针的原理是,接受一个申请好的内存地址,构造一个保存在栈上的智能指针对象,当程序退出栈的作用域范围后,由于栈 ...

  6. 智能指针unique_ptr的用法

    unique_ptr是独占型的智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr,如下面错误用法: std::unique_pt ...

  7. 基于C/S架构的3D对战网络游戏C++框架 _05搭建系统开发环境与Boost智能指针、内存池初步了解

    本系列博客主要是以对战游戏为背景介绍3D对战网络游戏常用的开发技术以及C++高级编程技巧,有了这些知识,就可以开发出中小型游戏项目或3D工业仿真项目. 笔者将分为以下三个部分向大家介绍(每日更新): ...

  8. C++ 引用计数技术及智能指针的简单实现

    一直以来都对智能指针一知半解,看C++Primer中也讲的不够清晰明白(大概是我功力不够吧).最近花了点时间认真看了智能指针,特地来写这篇文章. 1.智能指针是什么 简单来说,智能指针是一个类,它对普 ...

  9. C++11智能指针读书笔记;

    智能指针是一个类对象,而非一个指针对象. 原始指针:通过new建立的*指针 智能指针:通过智能指针关键字(unique_ptr, shared_ptr ,weak_ptr)建立的指针 它的一种通用实现 ...

  10. 「C++」理解智能指针

    维基百科上面对于「智能指针」是这样描述的: 智能指针(英语:Smart pointer)是一种抽象的数据类型.在程序设计中,它通常是经由类型模板(class template)来实做,借由模板(tem ...

随机推荐

  1. 杭电OJ----1097:一个难题(c++)

    问题描述 lcy给feng5166,lwg,JGShining和Ignatius带来了一个难题:给了a和b,如何知道a ^ b.每个人都反对这个BT问题,所以lcy使问题比开始容易. 这个难题描述了: ...

  2. 机器学习算法-PCA降维技术

    机器学习算法-PCA降维 一.引言 在实际的数据分析问题中我们遇到的问题通常有较高维数的特征,在进行实际的数据分析的时候,我们并不会将所有的特征都用于算法的训练,而是挑选出我们认为可能对目标有影响的特 ...

  3. 安装蓝鲸paas社区版

    安装蓝鲸paas社区版http://docs.bk.tencent.com/bkce_install_guide/setup/get_ready.html#hostssystemctl stop fi ...

  4. 有关Servlet的面试题

    CGI(Common Gateway Interface),通用网管接口 通用网管接口,简称CGI,是一种根据请求信息动态产生回应内容的技术.通过CGI,web服务器可以根据请求的不同,启动不同的外部 ...

  5. Neo4j 图数据库查询

    Cypher 介绍 Cypher 介绍:作为Neo4j的查询语言,"Cypher"是一个描述性的图形查询语言,允许不必编写图形结构的遍历代码对图形存储有表现力和效率的查询.Cyph ...

  6. 有了链路日志增强,排查Bug小意思啦!

    在工作中,相信大家最怕的一件事就是听到有人在工作群艾特你:某某功能报错啦... 然后你就得屁颠屁颠的去服务器看日志,日志量少还好点,多的话找起来太麻烦了.不太容易直接定位到关键地方. 东找找西找找,好 ...

  7. 【Spring】Spring的数据库开发 - 2、Spring JdbcTemplate的常用方法(execute、update、query)

    Spring JdbcTemplate的常用方法 文章目录 Spring JdbcTemplate的常用方法 execute() update() query() 简单记录-Java EE企业级应用开 ...

  8. Sgu149 Computer Network

    Sgu149 Computer Network 题目描述 给你一棵N(N<=10000)个节点的树,求每个点到其他点的最大距离. 不难想到一个节点到其他点的最大距离为:max(以它为根的子树的最 ...

  9. js 前端词典对象的属性和值读取

    通常服务端返回比较奇葩的数据对象,不知道该怎么将这个对象转换为可用实体,想了很久,突发奇想想到了这么个方法. 需求是这样:企业有多个产品,产品有分为很几个种类.服务端有获取产品的接口,和单独获取产品种 ...

  10. python多线程和GIL全局解释器锁

    1.线程     线程被称为轻量级进程,是最小执行单元,系统调度的单位.线程切换需要的资源一般,效率一般.  2.多线程         在单个程序中同时运行多个线程完成不同的工作,称为多线程 3.并 ...