IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【Qt】智能指针 -> 正文阅读

[C++知识库]【Qt】智能指针

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.数据管理类持有强指针成员,其他类持有弱指针成员,强指针作为参数传递。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-05 10:59:30  更:2022-05-05 11:02:20 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/21 2:45:28-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码