QPointer
QPointer属于Qt对象模型的特性,本质是一个模板类,它为QObje提供了guarded pointer。当其指向的对象(必须是QObject及其派生类)被销毁时,它会被自动置NULL,原理是其对象析构时会执行QObject的析构函数,进而执行QObjectPrivate::clearGuards(this);,这也是基于其指向对象都继承自QObject的原因。
QPointer对QMetaObject的相关操作做了简单的封装,这里的基本思想是
在QPointer构造的时候调用QMetaObject::addGuard(&o),把T的指针加入QMetaObject内的一个哈希表中,
在QPointer析构的时候调用QMetaObject::removeGuard(&o),把T的指针从哈希表中删除。
QPointer<Test> t = new Test(); // Test类必须继承QObject
delete t; //对象被delete之后,t会自动置NULL,而不会成为悬挂(dangling)的野指针
if(t.isNull())
qDebug()<<"NULL";
运行后会输出NULL。
实际中,QPointer用于解决这样的问题:在其他地方都用到了某个指针,在这个指针的对象被delete后,将指针置为空,那么其他地方的指针会变为野指针。也就是在C++中有这样的代码:
Test* p1 = new Test();
Test* p2 = p1;
delete p1;
p1 = NULL;
if(t2)
qDebug()<<"t2不是NULL";
else
qDebug()<<"t2成为NULL";
运行结果是t2不是NULL,也就是说t2成为了野指针。
有了QPointer,可以这样解决这个问题:
Test* t1 = new Test();
QPointer<Test> t2 = t1;
delete t1;
t1 = NULL;
if(t2)
qDebug()<<"t2不是NULL";
else
qDebug()<<"t2成为NULL";
运行结果是t2成为NULL,t2不再是野指针了。当然这里的t1最好也用QPointer,不过重点是t2。
QScopedPointer
QScopedPointer类保存了一个指针,指向在堆上分配的对象,在对象销毁时delete这个指针。从scope这个词就可以知道对象指针在出了作用域后就会被delete掉,不必手动delete。这个智能指针只能在本作用域里使用,不希望被转让。因为它的拷贝构造和赋值操作都是私有的,与QObject及其派生类风格相同。
Test* p = new Test();
QScopedPointer<Test> p2(p);
p2.data()->foo();
p2.take()->foo();
if(p2 == NULL)
qDebug()<<"p2 is null";
else
qDebug()<<"p2 is not null";
运行结果是:p2==NULL
T *QScopedPointer::data() const返回指向对象的常量指针,QScopedPointer仍拥有对象所有权。
T *QScopedPointer::take()也是返回对象指针,但QScopedPointer不再拥有对象所有权,而是转移到调用这个函数的caller,同时QScopePointer对象指针置为NULL。注意:如果没有使用take,p2会成为野指针。
void QScopedPointer::reset(T *other = Q_NULLPTR):delete目前指向的对象,调用其析构函数,将指针指向另一个对象other,所有权转移到other。
有些运算符是不支持的,如赋值运算符:
QScopedPointer<int> i(new int(42));
i = new int(43); // 编译不通过
i.reset(new int(43)); // 正确
以上代码仅仅是用于处理new的情况,不能用于malloc和new []数组。
经常用于这样的代码风格:
class MyPrivateClass; // forward declare MyPrivateClass
class MyClass
{
private:
QScopedPointer<MyPrivateClass> privatePtr; // QScopedPointer to forward declared class
public:
MyClass(); // OK
inline ~MyClass() {} // VIOLATION - Destructor must not be inline
private:
Q_DISABLE_COPY(MyClass) // OK - copy constructor and assignment operators
// are now disabled, so the compiler won't implicitely generate them.
};
在Qt源码中,经常用于D指针,例如在qpainter.h中,有代码: QScopedPointer<QPainterPrivate> d_ptr,以后研究D指针时再深入探讨
QSharedDataPointer
更像普通的指针,也是用于堆上分配的对象,但它是一个计数型只能指针,可以自由拷贝和赋值,可以共享,当此对象被一个QSharedPointer指针指向时,计数加1,少一个QSharedPointer指针指向时,计数减1,一直到计数为0时,对象才会销毁。
同QScopePointer类似,QSharedPointer也会在离开作用域后删除指针。
QSharedPointer和QWeakPointer都是线程安全的,可以原子地操作指针,不同线程可以获取这两种指针指向的对象而不必加锁。
void QSharedPointer::clear():清除所指向的对象,如果是最后一个指向,那么指针会被delete。
data函数,isNull函数同QScopePointer功能一样。
Test* p = new Test();
QSharedPointer<Test> p1(p);
QSharedPointer<Test> p2(p1);
QSharedPointer<Test> p3(p1);
p1.clear();
p2.clear();
p3.clear();
qDebug()<<"3333333";
if(p1.isNull())
qDebug()<<"p1 is null";
else
qDebug()<<"p1 is not null";
return a.exec();
在Qt main函数中测试会遇到事件循环的问题,就是运行GUI程序的return a.exec()实际进入事件循环,没有离开作用域,这种情况下想销毁对象就需要所有指针都clear。上面的代码中,指针计数为3,所以必须三个指针都执行clear,然后才会销毁对象,即调用析构函数,然后三个指针都成为NULL。有一个指针没有clear就不会销毁对象。
假如不进事件循环,而是return 0,那么就是按作用域机制,不需要所有指针都clear也会销毁对象。