五、C++语言进阶:智能指针

2023-11-17

5 智能指针

  • 指针的危害
    指针未初始化
    野指针
    内存泄漏

智能指针的出现就是为了解决上面的问题。
智能指针基于这样的事实得以发挥作用:定义在栈中的智能指针,当超出其作用域时,会自动调用它的析构函数,从而可以释放其关联的内存资源。

5.1 分类

智能指针 进入C++标准的版本 STL头文件 boost头文件 说明
auto_ptr C++03 <memory> <boost/auto_ptr.hpp> 尽量避免使用
unique_ptr C++11 <memory> <boost/unique_ptr.hpp> 管理单个堆内存对象,独享所有权,不允许赋值和拷贝,不能用于管理数组对象
shared_ptr C++11 <memory> <boost/shared_ptr.hpp> 引用计数智能指针,共享所有权
weak_ptr C++11 <memory> <boost/weak_ptr.hpp> shared_ptr的观察者,只进行引用而不改变引用计数,用来解决shared_ptr的循环引用问题
scoped_ptr - - <boost/scoped_ptr.hpp> 作用域智能指针,功能与unique_ptr相似。
  • 本质:
    将指针封装为类对象成员,并在析构函数里删除指针指向的内存。
  • 不同:
    auto_ptrunique_ptrscoped_ptr马上删除。
    shared_ptr计数为0删除。
    weak_ptr不删除。

5.2 auto_ptr

  • 作用
    对作用域内的动态分配对象的自动释放
  • 基本类型
#include <iostream>
#include <memory>
using namespace std;
int main(){
    int* pn = new int(10);
    auto_ptr<int> ap(pn);
    cout << *ap << endl;
}
  • 类类型
#include <iostream>
#include <memory>
using namespace std;
class Test{
public:
    Test(){cout << __func__ << endl;}
    ~Test(){cout << __func__ << endl;}
    void Func(){cout << __func__ << endl;}
};
int main(){
    Test* pt = new Test;
    auto_ptr<Test> apt(pt);
    apt->Func();
}
  • 缺陷

(1)两个auto_ptr不能同时拥有同一个对象

#include <memory>
using namespace std;
int main(){
    int* pn = new int(10);
    auto_ptr<int> ap1(pn);
    auto_ptr<int> ap2(pn);
}

(2)auto_ptr不能管理数组指针

#include <memory>
using namespace std;
int main(){
    int *pa=new int[10];
    auto_ptr<int>ap(pa);
}

(3)auto_ptr被拷贝或被赋值后,失去对原指针的管理,这种情况被称为指针所有权传递。

a)赋值的情况

  • 实例
#include <iostream>
#include <memory>
using namespace std;
int main(){
    int* p = new int(10);
    auto_ptr<int> ap1(p);
    cout<< *ap1 <<endl;
    auto_ptr<int> ap2=ap1;
    cout<< *ap1 <<endl;
}

b)拷贝的情况

  • 实例
#include <iostream>
#include <memory>
using namespace std;
void Func(auto_ptr<int> ap){
    cout << *ap << endl;
}
int main(){
    int* p = new int(10);
    auto_ptr<int> ap(p);
    cout<< *ap <<endl;
    Func(ap);
    cout<< *ap <<endl;
}
auto_ptr不能作为容器对象,因为STL容器中的元素经常要支持拷贝,赋值等操作。
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
int main(){
    int*p = new int(10);
    auto_ptr<int> ap(p);
    vector<auto_ptr<int> > vec;
    vec.push_back(ap);
}

在这里插入图片描述
(4)auto_ptr不能作为容器对象,因为STL容器中的元素经常要支持拷贝,赋值等操作。

#include <iostream>
#include <memory>
#include <vector>
using namespace std;
int main(){
    int*p = new int(10);
    auto_ptr<int> ap(p);
    vector<auto_ptr<int> > vec;
    vec.push_back(ap);
}
  • 实例
#include <iostream>
#include <memory>

using namespace std;

class Test{
public:
    Test(){cout << __func__ << endl;}
    ~Test(){cout << __func__ << endl;}
    void func(int n)const{
    	cout << n << endl;
    }
};

namespace MySTL{
template <typename T>
class auto_ptr{
    T* p;
public:
    auto_ptr(T* p):p(p){}
    ~auto_ptr(){
    	delete p;
    }
    T* operator->(){
    	return p;
    }
    T& operator*(){
    	return *p;
    }
};
};

int main(){
    Test* pp = new Test;
    MySTL::auto_ptr<Test> p(new Test); //p智能指针
    p->func(10);
    p.operator->()->func(100); //p.operator->()返回的是p指针

    (*p).func(100);
    delete pp;

    int* arr = new int [10];
    delete [] arr;
}

结果:

Test
Test
10
100
100
~Test
~Test

分析:将指针封装为类对象成员,并在析构函数里删除指针指向的内存。

5.3 unique_ptr

  • 作用
    代替auto_ptr,不可复制与赋值。

5.4 scoped_ptr

  • 作用
    unique_ptr相似,在本作用域中使用,不能复制与赋值。
    在这里插入图片描述

5.5 shared_ptr

  • 作用
    解决unique_ptr禁用拷贝以及auto_ptr的缺陷

  • 实例

#include <iostream>
#include <memory>

using namespace std;

class Test {
public:
    Test() {
        cout << __func__ << endl;
    }
    ~Test() {
        cout << __func__ << endl;
    }
    void func(int n)const {
        cout << n << endl;
    }
};

namespace MySTL {
template <typename T>
class auto_ptr {
    T* p;
public:
    auto_ptr(T* p):p(p) {}
    ~auto_ptr() {
        delete p;
    }
    T* operator->() {
        return p;
    }
    T& operator*() {
        return *p;
    }
};

template<typename T>
class shared_ptr {
    struct Record {
        T* p;
        int count;
    };
    Record* record;
public:
    shared_ptr(T* p) {
        record = new Record{p,1};
    }
    shared_ptr(const shared_ptr& ptr) {
        record = ptr.record;
        ++record->count;
    }
    T* operator->() {
        return record->p;
    }
    T& operator*() {
        return *(record->p);
    }

    shared_ptr& operator=(const shared_ptr& ptr) {
        if(this == &ptr) return *this;
        if(record->p == ptr.record->p) return *this;
        --record->count;
        if(record->count == 0) {
            delete record->p;
            delete record;
        }
        record = ptr.record;
        ++record->count;
        return *this;
    }
    ~shared_ptr() {
        --record->count;
        if(0 == record->count) {
            delete record->p;
            delete record;
        }
    }
};
};
int main() {
    // Test* pp = new Test;
    MySTL::shared_ptr<Test> p(new Test); //p智能指针
    p->func(10);
    p.operator->()->func(100); //p.operator->()返回的是p指针

    (*p).func(100);
    // delete pp;

    // int* arr = new int [10];
    // delete [] arr;

    MySTL::shared_ptr<Test> p2 = p;
    p2->func(300);
    p->func(400);

    MySTL::shared_ptr<Test> p3(new Test);
    p3 = p;
    p3->func(500);
}

结果:

Test
10
100
100
300
400
Test
~Test
500
~Test

分析:
通过加入Record记录指向该块内存的指针数量,在析构时候避免重复释放内存。

  • 存在的问题:循环引用问题
  • 实例
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A{
public:
    shared_ptr<B> pb;
    A (){
        cout << __func__ << endl;
    }
    ~A (){
        cout << __func__ << endl;
    }
};
class B{
public:
    shared_ptr<A> pa;
    B (){
        cout << __func__ << endl;
    }
    ~B (){
        cout << __func__ << endl;
    }
};
int main(){
    shared_ptr<A> a(new A);
    shared_ptr<B> b(new B);
    //A和B出现循环引用
    a->pb = b;
    b->pa = a;
}

结果:

A
B

分析:
只构造不析构,A和B出现循环引用

5.6 weak_ptr

  • 作用
    解决shared_ptr循环引用问题
  • 实例
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A{
public:
    weak_ptr<B> pb;
    A (){
        cout << __func__ << endl;
    }
    ~A (){
        cout << __func__ << endl;
    }
};
class B{
public:
    weak_ptr<A> pa;
    B (){
        cout << __func__ << endl;
    }
    ~B (){
        cout << __func__ << endl;
    }
};
int main(){
    shared_ptr<A> a(new A);
    shared_ptr<B> b(new B);
    //A和B出现循环引用
    a->pb = b;
    b->pa = a;
}

结果:

A
B
~B
~A

智能指针weak_ptr主要用来协助shared_ptr。不参与引用计数,但是有以下好处:
1 打破递归的依赖关系
2 使用一个共享的资源但是不要所有权,不添加引用计数
3 避免悬空指针。

5.7 总结

  • 智能指针的特点
智能指针 管理同一个对象 可拷贝 可复制 所有权 成为容器元素 管理数组指针
auto_ptr NG OK OK 传递 NG NG
unique_ptr NG NG NG 独享 NG NG
scoped_ptr NG NG NG 独享 NG NG
shared_ptr NG OK OK 共享 OK NG
weak_ptr NG OK OK 共享 OK NG

虽然通过弱引用指针可以有效的解除循环引用,但这种方式必须在程序员能预见会出现循环引用的情况下才能使用,也可以是说这个仅仅是一种编译期的解决方案。

如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。
因此,不要认为只要使用了智能指针便能杜绝内存泄漏。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

五、C++语言进阶:智能指针 的相关文章

  • libtool 在 Ubuntu 13.04 上构建 thrift 0.9.1 时出错

    在 Ubuntu 13 04 上构建 thrift 0 9 1 支持 C C java C perl python 时出现此错误 configure 不带任何选项运行 make 不带任何选项运行 Making all in test mak
  • 为什么在 C++ 中声明枚举时使用 typedef?

    我已经很多年没有写过任何 C 了 现在我正试图重新开始 然后我遇到了这个并考虑放弃 typedef enum TokenType blah1 0x00000000 blah2 0X01000000 blah3 0X02000000 Toke
  • 为什么我收到“无法进行二进制日志记录”的信息。在我的 MySQL 服务器上?

    当我今天启动 MySQL 服务器并尝试使用以下命令进行一些更改时用于 MySQL 的 Toad http www quest com toad for mysql 我收到此消息 MySQL 数据库错误 无法进行二进制日志记录 消息 交易级别
  • MSMQ接收和删除

    是否有任何选项可以在读取消息后将其从 MSMQ 中删除 比如 接收 删除可以作为原子操作运行吗 听起来您想查看下一条消息 然后在处理完成后接收它 Message message Queue Peek Queue ReceiveById me
  • DataGridView 列中的数字文本框

    我有一个DataGridView 我想要它的第一列或任何所需的列 其中有textboxes在其中 成为NUMERIC ONLY 我目前正在使用这段代码 private void dataGridViewItems EditingContro
  • 从 PL/SQL 调用 shell 脚本,但 shell 以 grid 用户而非 oracle 身份执行

    我正在尝试使用 Runtime getRuntime exec 从 Oracle 数据库内部执行 shell 脚本 在 Red Hat 5 5 上运行的 Oracle 11 2 0 4 EE CREATE OR REPLACE proced
  • 虚拟并行端口模拟器

    在我的计算机网络课程中 我们应该通过使用本机寄存器 例如使用 outportb 等命令 来学习并行端口编程 我没有并行端口 因为我住在 2011 年 但想练习这些程序 我使用 dosbox 安装了旧的 Turboc 3 IDE 有没有一个程
  • Nhibernate:连接表并从其他表获取单列

    我有以下表格 create table Users Id uniqueidentifier primary key InfoId uniqueidentifier not null unique Password nvarchar 255
  • 从点云检测平面集

    我有一组点云 我想测试3D房间中是否有角落 所以我想讨论一下我的方法 以及在速度方面是否有更好的方法 因为我想在手机上测试它 我将尝试使用霍夫变换来检测线 然后我将尝试查看是否有三条线相交 并且它们也形成了两个相交的平面 如果点云数据来自深
  • 在 C 语言中替换宏内的宏

    我正在尝试使代码部分可重用 我下面的评论片段没有达到我想要的效果 define NAME ABC define LOG SIZE NAME LEN 我想LOG SIZE决心ABC LEN 我尝试过使用 但没能让它发挥作用 LOG SIZE在
  • 如果在代码中添加元素,“FindName”将不起作用

    在 WPF 应用程序中 如果在 XAML 中声明 ContentControl
  • 如何在 EF Core 2.1 中定义外键关系

    我的 DAL 使用 EF Core 2 1 这就是我的模型的样子 一名用户只能拥有一种角色 Role entity kind of master public class Role public int RoleId get set pub
  • C 与 C++ 中的 JNI 调用不同?

    所以我有以下使用 Java 本机接口的 C 代码 但是我想将其转换为 C 但不知道如何转换 include
  • 当 Verb="runas" 时设置 ProcessStartInfo.EnvironmentVariables

    我正在开发一个 C 应用程序 我需要创建变量并将其传递给新进程 我正在使用ProcessStartInfo EnvironmentVariables 新进程必须提升运行 因此我使用 Verb runas var startInfo new
  • 值和类型的简洁双向静态 1:1 映射

    我将从我想象如何使用我想要创建的代码开始 它不必完全像这样 但它是我在标题中所说的 简洁 的一个很好的例子 就我而言 它是将类型映射到相关的枚举值 struct bar foo
  • 使用 boost 异步发送和接收自定义数据包?

    我正在尝试使用 boost 异步发送和接收自定义数据包 根据我当前的实现 我有一些问题 tcpclient cpp include tcpclient h include
  • 在 C 中使用 #define 没有任何价值

    If a define没有任何价值地使用 例如 define COMMAND SPI 默认值是0吗 不 它的评估结果为零 从字面上看 该符号被替换为空 然而 一旦你有了 define FOO 预处理器条件 ifdef FOO现在将是真的 另
  • 无法加载 JavaHL 库。- linux/eclipse

    在尝试安装 Subversion 插件时 当 Eclipse 启动时出现此错误 Failed to load JavaHL Library These are the errors that were encountered no libs
  • 强制卸载 NFS 安装目录 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的答案
  • 如何知道 HTTP 请求标头值是否存在

    我确信这很简单 但是却让我感到厌烦 我在 Web 应用程序中使用了一个组件 它在 Web 请求期间通过添加标头 XYZComponent true 来标识自身 我遇到的问题是 如何在视图中检查此组件 以下内容不起作用 if Request

随机推荐

  • PHP第三课:流程判断(下)

    上期PHP 我们学会了if判断 今天我们来学switch 看下这串代码
  • java算法题两个数组合并_算法:两个有序数组合并成一个有序数组 java语言

    题目 有两个有序数组a 和b 将它们合并成数组c 需要c 也是有序数组 思路 新建一个以两个集合长度之和为长度的新数组 从两数组最左边开始比起 把小的放入新集合 并用变量标记后一位置 每次比较都是比较的最左边未比较过的元素 通过变量 循环比
  • 用python计算灰度图像中掩模的面积(以像素为单位)

    下面是一个使用OpenCV的方法 我们用Otsu的阈值来获得一个二值图像 这个图像的前景对象是白色的 背景是黑色的 从这里我们使用cv2 countNonZero 它返回掩码上的白色像素数 找到白色像素的数量 pixels cv2 coun
  • Linux中的O_RDONLY、O_WRONLY、O_RDWR、O_APPEND、O_TRUNC、O_EXCL、O_EXCL、O_SYNC、O_NONBLOCK

    2023年7月11日 周二下午 这几个常量被定义在头文件fcntl h中 fcntl 是 file control 的缩写 它是由 file 文件 和 control 控制 两个单词组合而成的 在介绍这几个常量之前 要先介绍一下open函数
  • 【CTF】初学ROP

    在CTF PWN的题型中有一种利用方式是ROP 原理学明白了 但是实操起来一直不太理解ROP链的具体构造 为了弄明白原理 就找了一道入门题目 对照着wp进行单步调试 来理解ROP链的构造 题目 buuctf PicoCTF 2018 rop
  • slice+append陷阱

    1 前言 今天在网上看slice扩容原理 偶然看到一个slice的题目 感觉很有意思 题目如下 package main import fmt func main s1 int 1 2 s2 s1 s2 append s2 3 Test1
  • 用Python生成化学结构式

    from rdkit import Chem from rdkit Chem Draw import rdMolDraw2D print 欢迎使用化学式绘制工具 while 1 绘制主链 print 示例 CCC C CC 是第三个碳原子上
  • 3. C++ 11特性 数组和结构初始化、数组、循环、作用域内枚举

    目录 1 数组初始化 2 字符串初始化 3 结构初始化 4 数组替代模板类array 5 基于范围的for循环 6 作用域内枚举 1 数组初始化 初始化数组时 可省略等号 double earnings 4 1 2e4 1 6e4 1 1e
  • SpringMVC:从入门到精通,7篇系列篇带你全面掌握--七.自定义注解

    目录 Welcome Huihui s Code World 一 Java注解简介 1 原生注解的分类 1 JDK基本注解 Override SuppressWarnings value unchecked 2 JDK元注解 Retenti
  • chatgpt赋能python:用Python计算e的方法

    用Python计算e的方法 Python是一种功能强大的编程语言 可以用它来解决许多数学问题 其中之一就是计算数学常数e e是一个无理数 约为2 71828 它在许多数学和科学领域中都有重要应用 例如微积分 概率论和物理学 什么是e e是一
  • qt无边框窗体的移动

    无边框窗体的移动 this gt setWindowFlags Qt FramelessWindowHint 设置窗体为无边框 鼠标移动窗体移动涉及到三个事件分别是 鼠标按下 鼠标移动 鼠标松开 这三个事件在Qwidget中是虚函数 在QW
  • 人工智能复习

    大纲 5个大题 知识表示 一阶谓词逻辑 产生式 框架表示法 知识推理 归结原理 搜索算法 第三 四题 宽度优先算法 深度优先算法 开放题 论述题 计算题 用归结反演求取问题的答案 已知 张 Zhang 和李 Li 是同班同学 如果x和y是同
  • 超全总线控制方式总结

    总线控制 看了网络上的文章 质量真的不太行 就自己结合着整理一下 总线控制主要是两个大方面 总线判优控制和总线通信控制 一 总线判优控制 总线上同一时刻只允许一个设备进行占用 为了防止冲突 我们引入了判优逻辑 仲裁谁可以先占用 实际上我也没
  • Java基础12--面向对象:封装

    Java基础12 面向对象 封装 在面向对象程式设计方法中 封装 英语 Encapsulation 是指一种将抽象性函式接口的实现细节部分包装 隐藏起来的方法 封装可以被认为是一个保护屏障 防止该类的代码和数据被外部类定义的代码随机访问 要
  • chatGLM介绍

    一 简述 清华大学推出的ChatGLM的第二代版本 支持中文 效果好 清华大学的ChatGLM应该是中文大语言模型中最好的 要求低 可以在消费级的显卡上运行 二 链接 工程 https github com THUDM ChatGLM2 6
  • Ajax提交form表单

    Ajax提交form表单 ajax 几个参数需要注意一下 type POST 方法类型 传递方式 dataType json 预期服务器返回的数据类型 url url 就是form里面的action 不要忘了url前面加 data form
  • 基于OpenCV的人脸识别系统

    人脸识别技术是计算机视觉领域的重要应用之一 它可以通过对人脸图像的分析和比较 实现对不同人的身份的自动识别 在本文中 我们将使用OpenCV这一流行的计算机视觉库 介绍一个简单的人脸识别系统 并且逐步深入到算法的具体实现方式 1 安装与配置
  • 京东Java研发值不值得去?

    有问有答是程序视界的一个免费问答栏目 感兴趣的朋友 可以按照下面的方式参与 在本文后留言 以有问有答开始 放心 这类留言不会被公开 只有被选中的留言会出现在周四晚发布的文章内 同时可根据要求匿名 或发邮件给 foruok 163 com 标
  • linux 服务启动依赖,linux下的系统服务介绍——init、systemd

    我们经常会听到服务service以及daemon这两个词 它们到底是什么意思呢 之间有什么区别和联系吗 linux下的服务service是常驻在内存中的程序 并且能够提供一些系统或网络方面的功能 而daemon从字面翻译是守护进程或后台进程
  • 五、C++语言进阶:智能指针

    5 智能指针 指针的危害 指针未初始化 野指针 内存泄漏 智能指针的出现就是为了解决上面的问题 智能指针基于这样的事实得以发挥作用 定义在栈中的智能指针 当超出其作用域时 会自动调用它的析构函数 从而可以释放其关联的内存资源 5 1 分类