来源
还有其他一些,做了一些汇总和测试,就不全列了。
文章目录:
一、垂悬指针的问题
二、Qt中的智能指针
1、QPointer
2、QSharedPointer & QWeakPointer
3、QScopedPointer
4、其他智能指针
三、实践记录
四、用法举例
1、QWeakPointer举例
2、QScopedPointer 与 std::unique_ptr 举例
3、测试样例
代码中出现一个bug,最终发现是由于在某个特殊情况下出现了使用垂悬指针,造成了程序崩溃,进而学习了解了Qt的智能指针机制。
如图,有两个指针a和b指向同一片内存,如果删除其中一个指针a,再去使用指针b的话,程序会崩溃。因为指针b此时已经是一个垂悬指针(Dangling pointer)了,它指向的内存已经被释放不再有效。
垂悬指针使用指针b之前先判断b是否为空,这个做法在这里是不起作用的。问题的本质是通过指针a去释放内存时,指针b没有同步地置为空。
假如指针b能随内存的释放而自动置为空就好了,这正是智能指针所要解决的问题。
Qt提供了若干种智能指针:QPointer、QSharedPointer、QWeakPointer、QScopedPointer、QScopedArrayPointer、QSharedDataPointer、QExplicitlySharedDataPointer。
注:1、笔者Qt版本为4.8; 2、下述示例代码中"Plot"为"QObject"类的子类。
QPointer只用于QObject的实例。如果它指向的对象被销毁,它将自动置空。
如图:
QPointer这是Qt体系下的专门用于QObject的智能指针。常见使用方法:
QPointer<Plot> a(new T()); //构造
QPointer<Plot> a(b); //构造
a.isNull(); //判空
a.data(); //返回裸指针
QSharedPointer是引用计数(强)指针,当所有强指针销毁时,实际对象才会销毁。QWeakPointer是弱指针,可以持有对QSharedPointer的弱引用。它作为一个观察者,不会引起实际对象销毁,当对象销毁时会自动置空。
这两种指针同时都有以下3个成员:强引用计数strongRef,弱引用计数weakRef和数据data。
Qt引用计数指针
QWeakPointer两种指针分别对应于C++中的std::shared_ptr和std::weak_ptr。常见使用方法://构造
QSharedPointer<Plot> a(new Plot());
QSharedPointer<Plot> b = a;
QWeakPointer<Plot> c = a; //强指针构造弱指针
QWeakPointer<Plot> d(a);
//使用
c.clear(); //清除
a.isNull(); //判空
a->func(...); //(按常规指针来使用 "->")
QSharedPointer<Plot> e = d.toStrongRef(); //弱指针转为强指针。注意,弱指针无法操纵数据,必须转为强指针
QWeakPointer<Plot> f = e.toWeakRef();//强指针显式转为弱指针
QSharedPointer<Plot> g = e.dynamicCast<T>(); //动态类型转换
QScopedPointer保证当当前范围消失时指向的对象将被删除。它拥有一个很好的名字,它向代码的阅读者传递了明确的信息:这个智能指针只能在本作用域里使用,不希望被转让,因为它的拷贝构造和赋值操作都是私有的。相当于C++中的std::unique_ptr,实例代码:
func(){
Plot* plot = new Plot();
//QScopedPointer出作用域自动销毁内存
QScopedPointer<Plot>qsp(plot);
//plot没有内存泄漏
}
QScopedArrayPointer:一个QcopedPointer,默认删除它指向Delete []运算符的对象。为方便起见,还提供了操作符[]。QSharedDataPointer/QExplicitySharedDataPointer搭配QSharedData类一起使用,以实现自定义隐式共享或显式共享类。
1、通常,要使用弱指针,必须将其转换为强指针,因为这样的操作确保了只要您使用它就会生存。这相当于“锁定”访问的对象,并且是使用弱指针指向的对象的唯一正确方法。并且转换后使用前需要判空。
QSharedPointer<Plot> qsp = qwp.toStrongRef(); //qwp是QWeakPointer
if(!qsp.isNull()){
qDebug() << qsp->getName(...); //使用指向的对象
//...
}
2、最好在new的时候就用QSharedPointer封装,并管理起来。
QSharedPointer<Plot> qsp = QSharedPointer(new Plot());
3、使用智能指针包装后,不要直接去删除指针对象。
Plot* plot = new Plot();
QSharedPointer<Plot> qsp1(plot);
delete plot; //运行时会提示:"shared QObject was deleted directly. The program is malformed and may crash."
4、不要多次使用同一裸指针构造QSharedPointer。Plot *plot = new Plot();
QSharedPointer<Plot> qsp(plot);
QSharedPointer<Plot> qsp_ok = qsp;
QSharedPointer<Plot> qsp_error(plot); //崩溃,输出: “pointer 0x1f0a8f0 already has reference counting”
5、不要使用new直接构造QWeakPointer对象,它只能通过QSharedPointer的赋值来创建。QWeakPointer
6、由于智能指针对象是值语义,参数传递时尽可能用const引用兼顾效率
7、关于动态转换。使用QSharedPointer::dynamicCast()方法。
8、关于智能指针与QVariant转换,并关联到qobject的userdata。//注册到元对象
Q_DECLARE_METATYPE(QWeakPointer<Plot>)
//设置数据
item->setData(QVariant::fromValue(plot.toWeakRef()), Qt::UserRole);
//取数据
QWeakPointer<Plot> plot = treeModel->data(index, Qt::UserRole).value<QWeakPointer<BasePlot> >();
9、关于Qt元对象系统自动析构和Qt智能指针自动析构相冲突的问题,经初步实验Qt4.8中应该已经解决了?不过实际中,可以让数据用智能指针管理,不用父子层级;窗体控件用父子层级,不用智能指针。
QWeakPointer不能用于直接取消引用指针,但它可用于验证指针是否已在另一个上下文中被删除。并且QWeakPointer对象只能通过QSharedPointer的赋值来创建。
需要注意的是,QWeakPointer不提供自动转换操作符来防止错误发生。即使QWeakPointer跟踪指针,也不应将其视为指针本身,因为它不能保证指向的对象保持有效。
说了那么多,QWeakPointer到底有什么用呢?
答案就是:解除循环引用。
在概述中我们说到,QWeakPointer 是为配合 QSharedPointer 而引入的一种智能指针。而什么叫循环引用,就是说:两个对象互相使用一个 QSharedPointer成员变量指向对方(你中有我,我中有你)。由于QSharedPointer是一个强引用的计数型指针,只有当引用数为0时,就会自动删除指针释放内存,但是如果循环引用,就会导致QSharedPointer指针的引用永远都不能为0,这时候就会导致内存无法释放。
所以QWeakPointer诞生了,它就是为了打破这种循环的。并且,在需要的时候变成QSharedPointer,在其他时候不干扰QSharedPointer的引用计数。它没有重载 * 和 -> 运算符,因此不可以直接通过 QWeakPointer 访问对象,典型的用法是通过 lock() 成员函数来获得 QSharedPointer,进而使用对象。
示例
首先,我们来看一个QSharedPointer循环使用的示例:
#include
#include
#include
#include
class Children;
class Parent
{
public:
~Parent(){
qDebug() << __FUNCTION__;
}
QSharedPointer<Children> m_pChildren;
};
class Children
{
public:
~Children(){
qDebug() << __FUNCTION__;
}
QSharedPointer<Parent> m_pParent;
};
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
void test();
};
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
test();
}
Widget::~Widget()
{
qDebug() << __FUNCTION__;
}
void Widget::test()
{
QSharedPointer<Parent> parent(new Parent());
QSharedPointer<Children> children(new Children());
if(parent && children){
parent->m_pChildren = children;
children->m_pParent = parent;
}
}
在构造函数中调用test()函数,执行完过后应该会自动释放parent和children对象,但是由于相互引用,在退出之前,引用计数为2,退出之后引用计数还是1,所以导致不能自动释放,并且此时这两个对象再也无法访问到。运行过后发现并没有进入到Parent和Children的析构函数中。这就导致了内存泄漏。
那么该如何解决这个问题呢?
很简单,直接将上述代码中的
QSharedPointer<Children> m_pChildren;
QSharedPointer<Parent> m_pParent;
改成
QWeakPointer<Children> m_pChildren;
QWeakPointer<Parent> m_pParent;
这时候再次运行,就会看到输出:
~Children
~Parent
这是因为在test()退出之前引用计数是1,函数退出之后就自动析构,这就解除了上面的循环引用。
它们概念上应该是是一样的。下面不再区分:
这是一个很类似auto_ptr的智能指针,它包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除。但它的所有权更加严格,不能转让,一旦获取了对象的管理权,你就无法再从它那里取回来。
无论是QScopedPointer 还是 std::unique_ptr 都拥有一个很好的名字,它向代码的阅读者传递了明确的信息:这个智能指针只能在本作用域里使用,不希望被转让。因为它的拷贝构造和赋值操作都是私有的,这点我们可以对比QObject及其派生类的对象哈。
用法 (来自Qt的manual):
考虑没有智能指针的情况,
void myFunction(bool useSubClass)
{
MyClass *p = useSubClass ? new MyClass() : new MySubClass;
QIODevice *device = handsOverOwnership();
if (m_value > 3) {
delete p;
delete device;
return;
}
try {
process(device);
}
catch (...) {
delete p;
delete device;
throw;
}
delete p;
delete device;
}
我们在异常处理语句中多次书写delete语句,稍有不慎就会导致资源泄露。采用智能指针后,我们就可以将这些异常处理语句简化了:
void myFunction(bool useSubClass)
{
QScopedPointer<MyClass> p(useSubClass ? new MyClass() : new MySubClass);
QScopedPointer<QIODevice> device(handsOverOwnership());
if (m_value > 3)
return;
process(device);
}
另,我们一开始的例子,也是使用这两个指针的最佳场合了(出main函数作用域就将其指向的对象销毁)。
注意:因为拷贝构造和赋值操作私有的,它也具有auto_ptr同样的“缺陷”——不能用作容器的元素。
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
//raw pointer
//QString *p = new QString("hello");
QSharedPointer<QString> a(new QString("hello"));
//QSharedPointer b = a;
QWeakPointer<QString> c = a; //强指针构造弱指针
QWeakPointer<QString> d(a);
//使用
a.clear();
//c.clear(); //清除
if(!c.toStrongRef().isNull())
{
qDebug() << c.isNull() << c.toStrongRef()->length();
}
else {
qDebug() << "is null";
}
qDebug() << a.isNull(); //判空
qDebug() << a->length()<<*(a.data()); //(按常规指针来使用 "->")
QSharedPointer<QString> e = d.toStrongRef(); //弱指针转为强指针。注意,弱指针无法操纵数据,必须转为强指针
QWeakPointer<QString> f = e.toWeakRef();//强指针显式转为弱指针
QSharedPointer<QString> g = e.dynamicCast<QString>(); //动态类型转换
// QScopedPointer t2(p);
// QScopedPointer t3(p);
// t3.reset();
// qDebug() << t3.data();
// qDebug() << *(t1.data());
// qDebug() << *(t2.take()->data());
//Implements non-reference-counted strong pointer
// QScopedPointer pScopedPointer(new QString("Scoped"));
// // Build error, can NOT be shared and reference-counted
// //QScopedPointer pScopedPointerpScopedPointer2 = pScopedPointer;
// //Implements reference-counted strong sharing of pointers
// QSharedPointer pSmart(new QString("Smart"));
// QSharedPointer pSmart2;
// pSmart2 = QSharedPointer(new QString("smart 2"));
// QSharedPointer pSharedPoninter;
// // can be shared safely and reference-counted
// pSharedPoninter = pSmart;
// qDebug() << *(pSmart.data());
// qDebug() << *(pSmart2.data());
// qDebug() << *(pSharedPoninter.data());
// qDebug() << pSharedPoninter.stro
// QTimer *t = new QTimer;
// QSharedPointer timer(t);
// QWeakPointer pWeakPointer = timer;
// pWeakPointer.data()->start(2000);
// //Weak pointer's resources can be deleted from outside world
// delete t;
// qDebug() << timer.data();
// if (pWeakPointer.isNull())
// {
// qDebug() << "contained QObject has been deleted";
// }
}