【Qt】智能指针

2023-11-19

https://zhuanlan.zhihu.com/p/364014571?ivk_sa=1024320u

代码中出现一个bug,最终发现是由于在某个特殊情况下出现了使用垂悬指针,造成了程序崩溃,进而学习了解了Qt的智能指针机制。

一、悬垂指针的问题
如图,有两个指针a和b指向同一片内存,如果删除其中一个指针a,再去使用指针b的话,程序会崩溃。因为指针b此时已经是一个垂悬指针(Dangling pointer)了,它指向的内存已经被释放不再有效。

垂悬指针
使用指针b之前先判断b是否为空,这个做法在这里是不起作用的。问题的本质是通过指针a去释放内存时,指针b没有同步地置为空。

假如指针b能随内存的释放而自动置为空就好了,这正是智能指针所要解决的问题。

二、Qt中的智能指针
Qt提供了若干种智能指针:QPointer、QSharedPointer、QWeakPointer、QScopedPointer、QScopedArrayPointer、QSharedDataPointer、QExplicitlySharedDataPointer。

注:1、笔者Qt版本为4.8; 2、下述示例代码中"Plot"为"QObject"类的子类。
1、QPointer
QPointer只用于QObject的实例。如果它指向的对象被销毁,它将自动置空。如图:

QPointer
这是Qt体系下的专门用于QObject的智能指针。常见使用方法:

QPointer a(new T()); //构造
QPointer a(b); //构造
a.isNull(); //判空
a.data(); //返回裸指针
2、QSharedPointer & QWeakPointer

QSharedPointer是引用计数(强)指针,当所有强指针销毁时,实际对象才会销毁。

QWeakPointer是弱指针,可以持有对QSharedPointer的弱引用。它作为一个观察者,不会引起实际对象销毁,当对象销毁时会自动置空。

这两种指针同时都有以下3个成员:强引用计数strongRef,弱引用计数weakRef和数据data。

Qt引用计数指针

QSharedPointer & QWeakPointer
两种指针分别对应于C++中的std::shared_ptr和std::weak_ptr。常见使用方法:

//构造
QSharedPointer a(new Plot());
QSharedPointer b = a;
QWeakPointer c = a; //强指针构造弱指针
QWeakPointer d(a);

//使用
c.clear(); //清除
a.isNull(); //判空
a->func(…); //(按常规指针来使用 “->”)
QSharedPointer e = d.toStrongRef(); //弱指针转为强指针。注意,弱指针无法操纵数据,必须转为强指针
QWeakPointer f = e.toWeakRef();//强指针显式转为弱指针
QSharedPointer g = e.dynamicCast(); //动态类型转换
3、QScopedPointer

QScopedPointer保证当当前范围消失时指向的对象将被删除。它拥有一个很好的名字,它向代码的阅读者传递了明确的信息:这个智能指针只能在本作用域里使用,不希望被转让,因为它的拷贝构造和赋值操作都是私有的。

相当于C++中的std::unique_ptr,实例代码:

func(){
Plot* plot = new Plot();
//QScopedPointer出作用域自动销毁内存
QScopedPointerqsp(plot);
//plot没有内存泄漏
}
4、其他智能指针

QScopedArrayPointer:一个QcopedPointer,默认删除它指向Delete []运算符的对象。为方便起见,还提供了操作符[]。
QSharedDataPointer/QExplicitySharedDataPointer搭配QSharedData类一起使用,以实现自定义隐式共享或显式共享类。(参考:链接1,链接2)
三、实践记录
1、通常,要使用弱指针,必须将其转换为强指针,因为这样的操作确保了只要您使用它就会生存。这相当于“锁定”访问的对象,并且是使用弱指针指向的对象的唯一正确方法。并且转换后使用前需要判空。

QSharedPointer qsp = qwp.toStrongRef(); //qwp是QWeakPointer
if(!qsp.isNull()){
qDebug() << qsp->getName(…); //使用指向的对象
//…
}
2、最好在new的时候就用QSharedPointer封装,并管理起来。

QSharedPointer qsp = QSharedPointer(new Plot());
3、使用智能指针包装后,不要直接去删除指针对象。

Plot* plot = new Plot();
QSharedPointer qsp1(plot);
delete plot; //运行时会提示:“shared QObject was deleted directly. The program is malformed and may crash.”
4、不要多次使用同一裸指针构造QSharedPointer。

Plot *plot = new Plot();
QSharedPointer qsp(plot);
QSharedPointer qsp_ok = qsp;
QSharedPointer qsp_error(plot); //崩溃,输出: “pointer 0x1f0a8f0 already has reference counting”
5、不要使用new直接构造QWeakPointer对象,它只能通过QSharedPointer的赋值来创建

QWeakPointer a(new Plot()); //error: 引用计数:强-1 弱2
6、由于智能指针对象是值语义,参数传递时尽可能用const引用兼顾效率

7、关于动态转换。使用QSharedPointer::dynamicCast()方法。

8、关于智能指针与QVariant转换,并关联到qobject的userdata。

//注册到元对象
Q_DECLARE_METATYPE(QWeakPointer)
//设置数据
item->setData(QVariant::fromValue(plot.toWeakRef()), Qt::UserRole);
//取数据
QWeakPointer plot = treeModel->data(index, Qt::UserRole).value<QWeakPointer >();
9、关于Qt元对象系统自动析构和Qt智能指针自动析构相冲突的问题,经初步实验Qt4.8中应该已经解决了?不过实际中,可以让数据用智能指针管理,不用父子层级;窗体控件用父子层级,不用智能指针。

2022/4/10更新:

搞了一个源码,写了8个case示例,如果想学习,可以粘贴到qt工程里,这个可以直接运行,在main函数中修改你想运行的案例。

//----main.cpp-----
#include
#include
#include

/----------------定义数据类Plot----------------------/
class Plot {
public:
Plot() {qDebug()<<“Plot()”;m_value=0;}
virtual Plot(){qDebug()<<"Plot()";}

int getValue() const;
void setValue(int value);

protected:
int m_value;
};

inline int Plot::getValue() const
{
return m_value;
}

inline void Plot::setValue(int value)
{
m_value = value;
}

/------------------定义子类ColorPlot----------------------/
class ColorPlot : public Plot {
public:
void print(){qDebug() << "The Value is " << m_value;}
};

/------------------测试例子----------------------/
///裸指针
void case00(){
Plot* f = new Plot;
f->setValue(7);
qDebug() << f->getValue();

//对象f没有被析构,内存泄漏
//所以需要调用一次:
delete f;

}

///概述
void case01(){
Plot* f = new Plot;
QSharedPointer f1 = QSharedPointer(f);
f1->setValue(7);
qDebug() << f1->getValue();

//退出作用域时,智能指针对象f1被析构,
//它所“指向”的指针f已经没有任何其他智能指针“指向”它了,所以f的内存也自动析构了
//不会发生内存泄漏

}

///初始化
void case02(){
//最好在new的时候就用QSharedPointer封装,并管理起来,不要直接用裸指针
QSharedPointer f = QSharedPointer(new Plot());
f->setValue(7);
qDebug() << f->getValue();

//[tip] 1.使用智能指针包装后,不要直接去删除指针对象。
//Qt会提示:"shared QObject was deleted directly. The program is malformed and may crash."

//[tip] 2.也不要多次使用同一裸指针构造QSharedPointer。
//crashed: “pointer 0x1f0a8f0 already has reference counting”

}

///判空与删除
void case03(){
QSharedPointer s1;
QSharedPointer s2 = QSharedPointer();
QSharedPointer s3 = QSharedPointer(new Plot());
qDebug() << s1.isNull();//true
qDebug() << s2.isNull();//true
qDebug() << s3.isNull();//false

//用'.'来使用智能指针自身的方法;用'->'使用指向的对象的方法,调用前需要先判空。
if(!s3.isNull()){
    s3->setValue(7);
    qDebug() << s3->getValue();
}
//用clear()清空s3的指向
s3.clear();
qDebug() << s3.isNull();//true

}

///引用计数(强)
void case04(){
QSharedPointer s1;
QSharedPointer s2 = QSharedPointer(new Plot());
s1 = s2; //让s1也指向s2所指向的内存区域
QSharedPointer s3 = QSharedPointer(s2);//让s3也指向s2所指向的内存区域

//通过调试能看到,一个QSharedPointer的内部保存有一个强引用计数和一个弱引用计数,即对指向的那一片内存的引用计数。
//此时,s1 s2 s3的引用计数都一样:强引用为3,弱引用为3
s1.clear();//s2 s3的引用计数都一样:强引用为2,弱引用为2
s2.clear();//s3的引用计数:强引用为1,弱引用为1
s3.clear();//内存释放

//只有s1,s2,s3的指向都被清空,实际对象才会被析构,内存才会被释放

}

///引用计数(强&弱)
void case05(){
QSharedPointer s1;
QSharedPointer s2 = QSharedPointer(new Plot());
s1 = s2; //让s1也指向s2所指向的内存区域

QWeakPointer<Plot> w1 = QWeakPointer<Plot>(s1);//初始化QWeakPointer
QWeakPointer<Plot> w2;
w1 = s1; //初始化QWeakPointer
QWeakPointer<Plot> w3(s2);

//s1 s2 w1 w2 w3都指向同一片内存,这片内存的引用计数:强引用为2,弱引用为4
s1.clear();//引用计数:强引用为2,弱引用为4
w1.clear();//引用计数:强引用为1,弱引用为3
s2.clear();//引用计数:强引用为0,弱引用为0

//无论w1 w2 w3怎么样,只有s1,s2的指向都被清空,
//Foo的这个对象才会被析构,w1 w2 w3会被自动clear,内存才会被释放
qDebug() << w3.isNull();//true

}

///弱指针
void case06(){
QSharedPointer s1 = QSharedPointer(new Plot());
QWeakPointer w1 = QWeakPointer(s1);
s1->setValue(7);

if(!s1.isNull()){
    qDebug() << s1->getValue();//7
}
if(!w1.toStrongRef().isNull()){
    //弱指针无法操纵数据,必须转为强指针
    //用toStrongRef()将弱引用转为强引用,来调用成员方法
    qDebug() << w1.toStrongRef()->getValue();//7
    w1.toStrongRef()->setValue(66);
    qDebug() << w1.toStrongRef()->getValue();//66
}

//[tip] 通常,要使用弱指针,必须将其转换为共享指针,因为这样的操作确保了只要您使用它就会生存。
//这相当于“锁定”访问的对象,并且是使用弱指针指向的对象的唯一正确方法。并且转换后使用前需要判空。

}

///类型转换
void case07(){
QSharedPointer s1 = QSharedPointer(new ColorPlot());
QSharedPointer s2 = s1.dynamicCast(); // dynamicCast()
if(!s2.isNull()){
s2->setValue(7);
s2->print();//调用子类方法
}
}

///参数传递
void case08(){
//TODO
}

/------------------主程序----------------------/
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
case08();
}

/------------------Extra----------------------/
//[tip] 1.让数据用智能指针管理;窗体控件用Qt父子层级(不用智能指针)。
//[tip] 2.不使用裸指针作为接口参数,代码里面最好没有用到QSharedPointer::data()或者QWeakPointer::data()
//[tip] 3.数据管理类持有强指针成员,其他类持有弱指针成员,强指针作为参数传递。

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

【Qt】智能指针 的相关文章

随机推荐

  • 【在centos7.6上配置编译环境的时候,输入make menuconfig 报错】

    Build dependency Please install ncurses Missing libncurses so or ncurses h Prerequisite check failed Use FORCE 1 to over
  • 移远EC800N开发板驱动安装卡死

    开发板 移远EC800N开发板 EC800X QuecPython EVB V1 0 系统 Windows 10 驱动 Quectel ASR Series UMTS LTE Windows10 USB Driver Customer V1
  • vector与list的比较

    本博客只作为自己笔记使用 vector和list的相同点 都是STL中的序列式容器 vector和list的不同点 1 底层结构 vector是一段连续的空间 用动态顺序表实现 而list是一个带头结点的双向链表 2 是否支持随机访问 ve
  • tensorflow人工智能项目-鸟类识别系统

    介绍 Python作业 机器学习 人工智能 模式识别课程 鸟类识别检测系统 这是一个鸟类识别项目 基于tensorflow 使用卷积神经网络实现对200种鸟类进行识别 在数据集中收集了200中鸟类图片 每种鸟类都有着40 60张图片 通过对
  • Mysql Replication与Connector/J原理(四)

    十九 Connector与Failover协议 Mysql Connector J支持failover协议 即Client链接失效时 将会尝试与其他host建立链接 这个过程对application是透明的 Failover协议是 Mult
  • json请求 post vue_vue基础之使用get、post、jsonp实现交互功能示例

    本文实例讲述了vue基础之使用get post jsonp实现交互功能 分享给大家供大家参考 具体如下 一 如果vue想做交互 引入 vue resouce 二 get方式 1 get获取一个普通文本数据 window nl ad func
  • C++:类对象的初始化,构造函数:无参、拷贝构造函数,类中const 成员的初始化,对象的构造顺序

    类的对象 类对象的初始化 构造函数 无参构造函数 拷贝构造函数 类中const 成员的初始化 对象的构造顺序 类对象的初始化 include
  • 解决Jenkins插件不能下载安装的问题

    安装到这一步 显示无法下载Jenkins插件 安装中升级站点 如果你还在安装过程中 遇见这个问题 你可以打开一个新的网页 输入网址http localhost 8080 pluginManager advanced 在最下面的升级站点 把其
  • wimax与anroid的困惑

    我要加入wimax组 研究wimax 但是 为什么是anroid平台呢 wimax和android有关系吗 我们是做wimax芯片的呀 难道wimax芯片上跑anroid系统 好像不太可能 经过分析 应该是xx公司要使用我们的wimax芯片
  • PR-RL:Portrait Relighting via Deep Reinforcement Learning

    文章目录 Title PR RL Portrait Relighting via Deep Reinforcement Learning 1 Article 1 1 Abstract and Introduction 1 2 Conclus
  • thttpd源码分析

    由于最近要自己实现一个嵌入式web服务器 所以开始了对嵌入式web服务器的相关学习 为了使自己对服务器了解更加深入 便找到了开源的服务器进行了相关学习 首先学习的是 thttpd thttpd 是一个小型的 HTTP 服务器 官方网址 ht
  • Elasticsearch 7.13.2启动成功,但无法访问?

    今天在linux服务器上配置了es环境 已经成功运行 如下 原因 elasticsearch出于安全策略考虑 默认仅开启了本地访问 需要额外配置远程访问 备注 生产环境请设置密码 且不要直接开放0 0 0 0 解决 在elasticsear
  • 图像的FFT变换

    一 实验设备 计算机 matlab软件 二 实验目的 1 理解并掌握图像的FFT变换的原理 2 学习使用matlab对图像进行FFT变换 三 实验原理 图像fft变换可以将图像空间域变为频率域 进而对频率域图像进行操作 这样会使操作变得简单
  • vue_cli4遇到的问题及解决

    vue cli4遇到的问题及解决 vue cli4遇到的问题及解决 新建项目时报错 vue cli4遇到的问题及解决 新建项目时报错 新建项目代码 vue create project name 报错信息图 解决办法 检查node版本与np
  • 嵌入式系统C语言编程小心使用局部变量

    问题 今天同事在写一个STM32上的程序时 总是遇到内存溢出的错误 结果发现是因为使用了一个局部变量导致的 因为C语言的局部变量被编译器自动放到栈区的空间 全局变量需要手动申请并释放空间 嵌入式系统的栈区本来就很小 而且要放进去的变量是一个
  • 使用CSS在浏览器中绘制虚拟仪表盘(2020-12-30更新)

    效果
  • 【idea】idea无法打开,常规报错的原因和解决方法

    2020 07 29 更新 mac下因破解无法打开的解决方案 删除 Users 你的名字 Library Preferences IntelliJIdea2019 3 idea vmoptions 添加的内容即可 原因一 老版本的idea没
  • b01lers CTF web 复现

    warmup 按照提示依次 base64 加密后访问 可以访问 flag txt 也就是 Li9mbGFnLnR4dA from base64 import b64decode import flask app flask Flask na
  • Spring基础3——AOP,事务管理

    导航 黑马Java笔记 踩坑汇总 JavaSE JavaWeb SSM SpringBoot 瑞吉外卖 SpringCloud SpringCloudAlibaba 黑马旅游 谷粒商城 目录 1 AOP简介 1 1 AOP概念 作用 方式
  • 【Qt】智能指针

    https zhuanlan zhihu com p 364014571 ivk sa 1024320u 代码中出现一个bug 最终发现是由于在某个特殊情况下出现了使用垂悬指针 造成了程序崩溃 进而学习了解了Qt的智能指针机制 一 悬垂指针