智能指针与引用计数详解(二)

2023-10-27

智能指针与引用计数详解(一)当中讲了智能指针还有改进的地方,下面具体问题具体分析。
一、智能指针的赋值方法改进
上一章的赋值方法中只要是赋值都是右操作数引用计数加一,左操作数引用计数减一。没有考虑过引用计数对象自赋值的情况。
比如按照上一章代码,在main函数中做一下修改:

    int *ip = new int(12);
    HasPtr ptr(ip, 20);           
    HasPtr ptr1(ptr);
    HasPtr ptr2(ptr);
    {
        HasPtr ptr3 = ptr1;         //上一章是HasPtr ptr3 = NULL
        ptr3 = ptr;           
    }

上述代码“ptr3 = ptr; ”中左操作对象和右操作对象引用计数对象是一样的。

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
    cout<<"HasPtr assignment rhs uptr= "<<rhs.m_uptr<<endl;
    cout<<"HasPtr assignment this uptr = "<<this->m_uptr<<endl;
    ++rhs.m_uptr->m_useCount;
    if (--m_uptr->m_useCount == 0) {
        delete m_uptr;
    }
    m_uptr = rhs.m_uptr;
    m_val = rhs.m_val;
    return *this;
}

输出结果:
HasPtr assignment rhs uptr= 0x149ac40
HasPtr assignment this uptr = 0x149ac40

从输出结果可知两个智能指针对象中的引用计数对象是相等的,那么这时候赋值函数中的右操作数加一、左操作数减一其实相当于同一个对象的引用计数加一再减一了,这种情况作者分析认为没必要执行这一步多余的操作。
所以给出修改建议:

HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
    cout<<"HasPtr assignment rhs uptr= "<<rhs.m_uptr<<endl;
    cout<<"HasPtr assignment this uptr = "<<this->m_uptr<<endl;
    int uPtrIsSmae = rhs.m_uptr == this->m_uptr;
    cout<<"HasPtr assignment uPtrIsSmae = "<<uPtrIsSmae<<endl;
    if (!uPtrIsSmae) {
        ++rhs.m_uptr->m_useCount;
        if (--m_uptr->m_useCount == 0) {
            delete m_uptr;
        }
        m_uptr = rhs.m_uptr;
        m_val = rhs.m_val;
    }
    return *this;
}

输出结果:
U_Ptr constructor
HasPtr constructor
HasPtr copy constructor m_uptr->m_useCount = 1
HasPtr copy constructor m_uptr->m_useCount = 2
HasPtr copy constructor m_uptr->m_useCount = 3
HasPtr assignment rhs uptr= 0x1ed4c40
HasPtr assignment this uptr = 0x1ed4c40
HasPtr assignment uPtrIsSmae = 1
HasPtr destruct m_uptr->m_useCount = 4
HasPtr destruct m_uptr->m_useCount = 3
HasPtr destruct m_uptr->m_useCount = 2
HasPtr destruct m_uptr->m_useCount = 1
U_Ptr destruct

二、智能指针传入参数扩展
之前的所有测试使用的都是int 指针,如果指针对象换了是不是得重写一个引用计数类呢?而且重写的类跟之前的设计是一模一样的,只是换一个指针对象而已,C++中有类模板技术,所有咱们可以将智能指针和引用计数类都写成模板类,这个就可以适配各种指针对象了。
直接上代码:
uPtr.h

#ifndef _UPtr_H
#define _UPtr_H
#include <iostream>
using namespace std;

template <typename T>
class HasPtr;

template <typename T>
class U_Ptr {
    private:
    friend class HasPtr<T>;

    U_Ptr(T *p);
    ~U_Ptr();
    
    T *m_ip;
    int m_useCount;
};

template <typename T> 
U_Ptr<T>::U_Ptr(T *p)
    :m_ip(p)
    ,m_useCount(1)
{

}

template <typename T> 
U_Ptr<T>::~U_Ptr()
{
    cout<<"U_Ptr destruct"<<endl;
    if (NULL != m_ip) {
    	delete m_ip;
    }
}
#endif

hasPtr.h

#ifndef _HasPtr_H
#define _HasPtr_H
#include "uPtr.h"
#include <iostream>
using namespace std;
template <class T>
class HasPtr {

    public:
        HasPtr(T *p, int i);
        HasPtr(const HasPtr &ptr);
        HasPtr& operator=(const HasPtr &rhs);
        ~HasPtr();
        T *get_ptr() const;
        void set_ptr(T *p);

        int get_int() const;
        void set_int(int i);

        T get_ptr_val() const;
        void set_ptr_val(T val);

    private:
        U_Ptr<T> *m_uptr;
        int m_val;

};
template <typename T>
HasPtr<T>::HasPtr(T *p, int i)
    :m_uptr(new U_Ptr<T>(p))
    ,m_val(i)
{
    cout<<"HasPtr constructor"<<endl;
}

template <typename T>
HasPtr<T>::HasPtr(const HasPtr &ptr)
    :m_uptr(ptr.m_uptr)
    ,m_val(ptr.m_val)
{
    ++m_uptr->m_useCount;
    cout<<"HasPtr copy constructor m_uptr->m_useCount = "<<m_uptr->m_useCount<<endl;
}

template <typename T>
HasPtr<T>& HasPtr<T>::operator=(const HasPtr &rhs)
{
    bool uptrIsSame = this->m_uptr == rhs.m_uptr;
    cout<<"HasPtr assignment rhs.m_uptr->m_useCount = "<<rhs.m_uptr->m_useCount<<endl;
    cout<<"HasPtr assignment m_uptr->m_useCount = "<<m_uptr->m_useCount<<endl;
    if (!uptrIsSame) {
        ++rhs.m_uptr->m_useCount;
        if (--m_uptr->m_useCount == 0) {
            delete m_uptr;
        }
        m_uptr = rhs.m_uptr;
        m_val = rhs.m_val;
    }
    return *this;
}

template <typename T>
T *HasPtr<T>::get_ptr() const {
    return m_uptr->m_ip;
}

template <typename T>
void HasPtr<T>::set_ptr(T *p) {
    m_uptr->m_ip = p;
}

template <typename T>
int HasPtr<T>::get_int() const {
    return m_val;
}

template <typename T>
void HasPtr<T>::set_int(int i) {
    m_val = i;
}

template <typename T>
T HasPtr<T>::get_ptr_val() const {
    return *m_uptr->m_ip;
}

template <typename T>
void HasPtr<T>::set_ptr_val(T val) {
    *m_uptr->m_ip = val;
}

template <typename T>
HasPtr<T>::~HasPtr() {
    cout<<"HasPtr destruct m_uptr->m_useCount = "<<m_uptr->m_useCount<<endl;
    if (--m_uptr->m_useCount == 0) {
        delete m_uptr;
        m_uptr = NULL;
    }
}
#endif

main函数

    int *p = new int(12);
    HasPtr<int> ptr(p, 20);
    HasPtr<int> ptr1(ptr);
    HasPtr<int> ptr2(ptr1);
    {
        HasPtr<int> ptr3 = ptr1;
        ptr3 = ptr;
    }

输出结果是一样的。

PS:这里边遇到一个坑,由于模板计数不熟悉,所以之前按照正常套路,在uPtr.h和hasPtr.cpp文件中定义对应.h文件中的函数,结果编译报错:

main.cpp:(.text+0x38): undefined reference to `HasPtr<int>::HasPtr(int*, int)'
main.cpp:(.text+0x4b): undefined reference to `HasPtr<int>::HasPtr(HasPtr<int> const&)'
main.cpp:(.text+0x5e): undefined reference to `HasPtr<int>::HasPtr(HasPtr<int> const&)'
main.cpp:(.text+0x71): undefined reference to `HasPtr<int>::HasPtr(HasPtr<int> const&)'
main.cpp:(.text+0x84): undefined reference to `HasPtr<int>::operator=(HasPtr<int> const&)'
main.cpp:(.text+0x90): undefined reference to `HasPtr<int>::~HasPtr()'
main.cpp:(.text+0xa1): undefined reference to `HasPtr<int>::~HasPtr()'
main.cpp:(.text+0xad): undefined reference to `HasPtr<int>::~HasPtr()'
main.cpp:(.text+0xb9): undefined reference to `HasPtr<int>::~HasPtr()'
main.cpp:(.text+0xd1): undefined reference to `HasPtr<int>::~HasPtr()

后面查询资料发现模板类的所有定义都必须放在头文件中。所以修改之后就可以了。
参考资料:
https://stackoverflow.com/questions/7731021/c-gcc-undefined-reference-to-stackintstackint

当然也有另外一种解决方案,就是编译的时候将对应的.h和.cpp都放在main.cpp中:

类似这样:
#include "hasPtr.h"
#include "hasPtr.cpp"

这种方法不推荐
参考资料:
https://stackoverflow.com/questions/13216844/undefined-reference-to-linkedlistintpush-frontint

扩展:
项目开发中,通常会用到继承,有继承就意味着很多地方会使用多态。比如现在包含的指针是一个有基类的指针对象,那么如果要用到该基类的另一个派生类难道要再重新new一个对象使用吗?具体方案请见智能指针与句柄详解

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

智能指针与引用计数详解(二) 的相关文章

  • std::vector简介及其使用(转)

    std vector简介及其使用 本文中的vector指的是std vector C 11标准 Vector概述 template
  • 关于C++中constexpr的用法

    在C 11 primer中 关于constexpr用法给出的解释是 允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式 声明为constexpr的变量一定是一个常量 而且必须用常量表达式初始化 第一句中 c
  • C++ bind与回调函数

    1 回调函数 注册回调函数里可以使用functional来统一接口 可以传入函数指针 lambda bind等 函数1 2 为一个模块 为回调函数 函数3为一个模块 为注册回调函数 形参为函数指针 注册回调函数的入参为函数指针 指向回调函数
  • C++之const类成员变量,const成员函数,const指针

    https www cnblogs com cthon p 9178701 html 结合下面这个链接观看更佳 讲常量指针和指向常量的指针的 https www cnblogs com lihuidashen p 4378884 html
  • C++ string类的实现

    个人简介 作者简介 大家好 我是菀枯 支持我 点赞 收藏 留言 格言 不要在低谷沉沦自己 不要在高峰上放弃努力 前言 在C语言中 没有专门用来表示字符串的类型 C语言的字符串是一系列以 0 为结尾的字符的集合 虽然C语言为这样的字符串提供了
  • ##顺序表 编码##

    ifndef LIST H define LIST H class List public List int size List 析构函数 void ClearList 清空线性表 bool ListEmpty 判断线性表是否为空 int
  • C++ 继承(1): 继承方式(public, protected, private)

    C 继承 1 继承方式 public protected private 继承中的特殊关系 隐藏 is a 在水一方xym的博客 CSDN博客 C 远征之继承篇 视频教程 笔记 方便自己查阅和复习 温故而知新 目录 1 c 继承简介 代码示
  • 第二章排错的工具:调试器Windbg(下)

    感谢博主 http book 51cto com art 200711 59874 htm 2 2 读懂机器的语言 汇编 CPU执行指令的最小单元 2 2 1 需要用汇编来排错的常见情况 汇编是CPU执行指令的最小单元 下面一些情况下 汇编
  • C++ 预处理器

    http www runoob com cplusplus cpp preprocessor html
  • STL——set容器、map容器

    初识STL set容器 multiset容器 set容器 构造和赋值 set容器 大小和交换 set容器 插入和删除 set容器的查找和统计 set和multiset的区别 set的相关操作源码 multiset的相关操作源码 pair使用
  • osgEarth的Rex引擎原理分析(一二三)osgEarth的缓存及其结构

    目标 十七 中问题43 1 缓存分两类 1 文件缓存 osgDB FileCache FileSystemCache 位于osgEarthDrivers cache filesystem FileSystemCache osgDB File
  • 关于C++对象模型(下)

    下篇主要讨论调用成员函数 访问成员变量的开销 及其特殊成员函数 数组 异常处理的讨论 这篇文章中出现的对象定义都出现在上篇中 全文在这里下载 文章内容转自 http tb blog csdn net TrackBack aspx PostI
  • C++学习记录6--srand(time(NULL)产生随机数

    time 函数 返回从1970 1 1 00 00 00到调用time 函数时所经过的时间 以秒为单位 所以是个整数 time NULL 或time 0 表示在内存中不存储返回的数值 头文件 include
  • [C++] IO流,文件输入输出,string流,类型转换运算符

    文章目录 类型转换运算符 循环输入原理 文件输入输出 简介 文件模式 二进制读写与文本文件读写 string 流 istringstream 使用 ostringstream 使用 类型转换运算符 循环输入原理 循环输入 有些oj题会要求我
  • C#各种结束进程的方法详细介绍

    转自http www cnblogs com zjoch p 3654940 html Process类的CloseMainWindow Kill Close Process CloseMainWindow是GUI程序的最友好结束方式 从名
  • ListView的操作

    转自http blog sina com cn s blog 43eb83b90100mhrs html 这一篇对我也不错http blog csdn net xiaohan2826 article details 8603015 小白叔叔
  • C++征途 --- List链表容器

    第一部分 基础概念 上面这个模型的是一个单向链表 优点 1 链表增加和删除节点的时候不需要进行vector数组那样的增完后进行后移 也不需要删完后前移 当它增加一个节点的时候 只需要将它插入的位置的上一个节点的指针域中的指针指向增加的节点
  • C++ primer智能指针(HasPtr)实现

    智能指针显然是C 吸引人的地方之一 必须掌握 看了 C primer 里面着重讲了智能指针的实现方式 书中说到 HasPtr 注 就是自定义的智能指针 在其它方面的行为与普通指针一致 具体而言 复制对象时 副本和原对象将指向同一基础对象 如
  • Ubuntu20.04中VSCode配置C++以及分文件编写配置

    网上搜索了很多文章 一直显示找不到自定义的头文件 今天总算捣鼓出来了 参考文章 https www cnblogs com icmzn p 16244665 html https blog csdn net qq 39048131 arti
  • 如何用结构体替代数组实现学生信息的录入与比较

    这里是一个有关学生学号 成绩信息的录入 输出成绩最高的学生信息 供参考学习 include

随机推荐

  • 蓝桥杯-模拟

    星期一 611 import datetime start datetime date 1901 1 1 end datetime date 2000 12 31 delta datetime timedelta days 1 s 0 wh
  • java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long 错误分析

    从内容中已经知道是类转换报错 但是为什么会报错呢 int类型是可以转换为long类型的 对这种低精度是可以直接转为高精度的 但是报错也明确的提示了 java lang Integer cannot be cast to java lang
  • HTML <strong> 标签

    定义和用法 以下元素都是短语元素 虽然这些标签定义的文本大多会呈现出特殊的样式 但实际上 这些标签都拥有确切的语义 我们并不反对使用它们 但是如果您只是为了达到某种视觉效果而使用这些标签的话 我们建议您使用样式表 那么做会达到更加丰富的效果
  • 实现在最新版本的cesium中引用叠加shp文件的类的功能

    因为刚接触cesium不久 对js的编码规范什么的也不是很懂 所以这么简单的问题就搞了好几天 不过总算有所突破了 网上看到这个文章 http blog sina com cn s blog 15e866bbe0102xxd1 html 里面
  • springboot+springcloud相关面试题

    什么是springboot 用来简化spring应用的初始搭建以及开发过程 使用特定的方式来进行配置 properties或yml文件 创建独立的spring引用程序 main方法运行 嵌入的Tomcat 无需部署war文件 简化maven
  • 学习笔记(103):R语言入门基础-数据点类型(type参数)

    立即学习 https edu csdn net course play 24913 285847 utm source blogtoedu type参数 type p 在图形中数据显示为点 type l 在图形中数据显示为线 type b
  • sort排序用法

    Python sorted函数 我们需要对List Dict进行排序 Python提供了两个方法对给定的List L进行排序 方法1 用List的成员函数sort进行排序 在本地进行排序 不返回副本方法2 用built in函数sorted
  • 在矩池云使用Llama2-7B的具体方法

    今天给大家分享如何在矩池云服务器使用 Llama2 7b模型 硬件要求 矩池云已经配置好了 Llama 2 Web UI 环境 显存需要大于 8G 可以选择 A4000 P100 3090 以及更高配置的等显卡 租用机器 在矩池云主机市场
  • 图像对比度,亮度

    很多时候 一张图像被过度曝光 显得很白 或者光线不足显得很暗 这个时候可以通过调节图像的这两个基本属性 亮度与对比度 来获得整体效果的提升 从而获得质量更高的图片 1 算子operator 首先我们给出算子的概念 一般的图像处理算子都是一个
  • 电源学习总结(五)——开关电源基本原理

    前面讲了一些线性稳压的原理和设计的基本方法 事实上 除了一些功率较大或者对精度要求较高的电源设计 使用集成的线性稳压芯片很少出现 翻车 事故 一般只需关注输入输出范围即可 此外 需注意由于集成的开关电源芯片 尤其是贴片封装的 如SOT 22
  • 【CUDA】初步了解PageLocked host memory的mapped memory功能使用

    导言 大家都知道CUDA 中PageLocked memory 相比portable memory 有着多种优势 在有front side bus的系统中 pagelocked memory 所提供的host 与device之间的数据传送速
  • 硬盘突然提示没有初始化_分享一下固态硬盘不认盘的修复方法

    写在开头 固态硬盘比较害怕突然停电 如果里面有重要数据 请勿用此方法尝试修复 即便可以成功 里面的数据也已经被抹除 需要恢复数据的话 还是需要找专业的数据恢复公司来做 切勿自己折腾 进入正题 前段时间淘了一块威刚的SP550 120G SA
  • 常用脚本(九)Unity_Input

    1 输出鼠标位置 在Update方法中 Debug Log Input mouseposition 2 判断鼠标是否点击 返回 True 和 false 每帧都输出 在Update方法中 Debug Log Input anykey 3 I
  • run()方法和start()方法的区别

    run 方法和start 方法的区别 文章目录 run 方法和start 方法的区别 一 start 是什么 二 run 是什么 三 具体代码实例 四 start 和run 方法的区别 参考 一 start 是什么 用 start方法来启动
  • 安全并正确地重启Elasticsearch集群

    文章目录 前言 问题原因其本质 提前准备 准备重启集群 更新集群 前言 elasticsearch本身具有高可用性 可以做到停机不停服务 在重启elasticsearch后可能存在数据丢失 或者是 启动ES后 怎么一直有大量的数据在迁移 问
  • 快速创建一个spring boot项目

    写了两年还在创建spring boot 项目 最近想自己尝试开发一个项目 所以随便记录一下吧 平常 工作都是现成的项目开发 在项目上加新功能之类的 除了工作平常回去也没琢磨 现在想多思考 为了国庆之后辞职 找工作做一个铺垫 分割线 选择一些
  • linux内核vmlinux生成过程简要分析

    最近工作不太忙 研究了一下Linux内核的编译过程 在此简要记录一下 obj zImage obj compressed vmlinux FORCE call if changed objcopy linux的内核 zImage 的生成依赖
  • 第二天(七)osg::Object* readObjectFile_const std::string& filename_const ReaderWriter::Options* options

    目前流程是 osgViewer viewBase frame viewerInit 创建帧事件 并将漫游器与事件和视口相关联 gt osgViewer Viewer ViewerInit gt osgViewer View Init gt
  • 电脑老是安装一些来路不明的软件(如何解决)?

    目录 先解决自身可能出现的问题 上四大方法 先解决自身可能出现的问题 1 自行百度下载软件 没有到官网那去下载 进入一些假官网下载软件会附带一些流氓软件 看好官网地址再下载或者用安全软件那去下载 2 电脑的浏览器被劫持了 浏览器会有小广告
  • 智能指针与引用计数详解(二)

    在智能指针与引用计数详解 一 当中讲了智能指针还有改进的地方 下面具体问题具体分析 一 智能指针的赋值方法改进 上一章的赋值方法中只要是赋值都是右操作数引用计数加一 左操作数引用计数减一 没有考虑过引用计数对象自赋值的情况 比如按照上一章代