一、QPointer
template <typename T>
class QPointer
受保护的指针(A guarded pointer) QPointer<T>
的行为类似于普通的 C++ 指针 T*
,但不同的是当所引用的对象被销毁时(普通的 C++ 指针则会变成“悬空指针”),QPointer
会自动清除。T
必须是 QObject
的子类。
当需要存储指向由其他人拥有的 QObject
的指针时,受保护的指针非常有用,因为在您仍然持有对它的引用时,该对象可能已被销毁。您可以安全地测试指针的有效性。
在 QWidget
(或 QWidget
的子类)上使用 QPointer
时,QPointer
会在 QObject 析构函数中清除(因为这是在清除 QWeakPointer
对象时进行的操作)。跟踪窗口小部件的任何 QPointers
都不会在 QWidget
析构函数销毁所跟踪的窗口小部件的子项之前被清除。
此外,Qt 还提供了 QSharedPointer
,这是一个实现了引用计数共享指针对象的类,可用于维护对单个指针的集合引用。
示例:
QPointer<QLabel> label = new QLabel;
label->setText("&Status:");
...
if (label)
label->show();
如果在此期间删除了QLabel
,那么 label 变量将保存nullptr
而不是无效的地址,最后一行代码将永远不会被执行。
QPointer
可用的函数和运算符与普通的非保护指针相同,除了指针算术运算符(+、-、++和--),通常只用于对象数组。
像普通指针一样使用QPointer
,您将不需要阅读此类的文档。
要创建保护指针,您可以从T*
或相同类型的另一个保护指针构造或赋值给它们。您可以使用operator==()
和operator!=()
将它们相互比较,或使用isNull()
测试是否为nullptr
。您可以使用*x
或x->member
表示法对它们进行解引用。
保护指针将自动转换为T*
,因此您可以自由地混合使用保护和非保护指针。这意味着如果您有一个QPointer<QWidget>
,您可以将其传递给需要QWidget*
的函数。因此,声明函数以接受QPointer
作为参数几乎没有价值;只需使用普通指针即可。在存储指针的时间使用QPointer
。
请注意,类T
必须继承自QObject
,否则将导致编译或链接错误。
二、QSharedPointer
template <typename T>
class QSharedPointer
QSharedPointer
是C++中的自动共享指针。它在正常情况下的行为与普通指针完全相同,包括对const
的尊重。
当QSharedPointer
对象超出其作用域并且没有其他QSharedPointer
对象引用它时,它将删除它所持有的指针。
可以从普通指针、另一个QSharedPointer
对象或通过将QWeakPointer
对象升级为强引用来创建QSharedPointer
对象。
QSharedPointer
通过外部引用计数(即放置在对象外部的引用计数器)来持有一个共享指针。正如其名称所示,指针值在QSharedPointer
和QWeakPointer
的所有实例之间共享。然而,指针所指向的对象的内容不应被视为共享:只有一个对象。因此,QSharedPointer
不提供分离或复制指向对象的方法。
可选指针跟踪:
QSharedPointer
的一个特性是可选的指针跟踪机制,可以在编译时启用以进行调试。当启用时,QSharedPointer
在一个全局集合中注册所有跟踪的指针。这样可以捕捉到将同一个指针分配给两个QSharedPointer
对象的错误。
在包含QSharedPointer
头文件之前,可以通过定义QT_SHAREDPOINTER_TRACK_POINTERS
宏来启用该功能。即使在没有启用该功能的代码编译中,也可以安全地使用该特性。QSharedPointer
将确保即使在没有指针跟踪的代码编译中,指针也会从跟踪器中移除。
然而,需要注意的是,指针跟踪功能在多重继承或虚继承(即两个不同的指针地址可以引用同一个对象的情况)上有一定的限制。在这种情况下,如果将指针转换为不同类型并且其值发生变化,QSharedPointer
的指针跟踪机制可能无法检测到正在跟踪的对象是相同的。
·引用计数器·浅解:
Qt智能指针QSharedPointer
与 C++中的std::shared_ptr
其作用是一样的,其应用范围比本文说到的QPointer
和QScopedPointer
更广。
QSharedPointer
是一个共享指针,它与 QScopedPointer 一样包装了new操作符在堆上分配的动态对象,但它实现的是引用计数型的智能指针 ,也就是说,与QScopedPointer不同的是,QSharedPointer可以被自由地拷贝和赋值,在任意的地方共享它,所以QSharedPointer也可以用作容器元素。
所谓的计数型指针,就是说在内部QSharedPointer对拥有的内存资源进行引用计数,比如有3个QSharedPointer同时指向一个内存资源,那么就计数3,当引用计数下降到0,那么就自动去释放内存啦。
需要注意的是:QSharedPointer 是线程安全的,因此即使有多个线程同时修改 QSharedPointer 对象也不需要加锁。虽然 QSharedPointer 是线程安全的,但是 QSharedPointer 指向的内存区域可不一定是线程安全的。所以多个线程同时修改 QSharedPointer 指向的数据时还要应该考虑加锁。
QSharedPointer创建与析构顺序的示例:
/* Student类声明 */
class Student : public QObject {
Q_OBJECT
public:
Student(QObject * parent = nullptr);
~Student();
};
/* Widget类声明 */
class Widget : public QWidget {
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
private:
QSharedPointer<Student> m_pStudent;
};
#include <QDebug>
/* Widget类实现 */
Widget::Widget(QWidget *parent) : QWidget(parent) {
qDebug() << __FUNCTION__;
m_pStudent = QSharedPointer<Student>(new Student());
}
Widget::~Widget() {
qDebug() << __FUNCTION__;
}
/* Student类实现 */
Student::Student(QObject *parent) : QObject (parent) {
qDebug() << __FUNCTION__;
}
Student::~Student() {
qDebug() << __FUNCTION__;
}
运行后「关闭窗口」,输出:
Widget
Student
~Widget
~Student
Qt有一种很好的处理内存管理的方法。核心思想很简单。大多数对象都有一个父对象,当父对象被销毁时,它将首先销毁所有子对象。使用这种技术,您通常可以编写Qt应用程序,几乎不必担心内存管理。通常,您的整个应用程序中实际上不需要一个delete
。这非常不错!
示例:下面定义一个‘父’类和一个‘子’类:
class Parent : public QObject {
Q_OBJECT
public:
Parent(QObject *parent = 0) : QObject(parent) {
std::cout << "Parent()" << std::endl;
}
virtual ~Parent() {
std::cout << "~Parent()" << std::endl;
}
};
class Child : public QObject {
Q_OBJECT
public:
Child(QObject *parent = 0) : QObject(parent) {
std::cout << "Child()" << std::endl;
}
virtual ~Child() {
std::cout << "~Child()" << std::endl;
}
};
➊正确的调用
void MyAwesomeFunction(Child *o) {
std::cout << "Using Object!" << std::endl;
}
int main() {
Parent *const parent = new Parent(0);
Child *const child = new Child(parent);
MyAwesomeFunction(child);
delete parent;
}
输出:
Parent()
Child()
Using Object!
~Parent()
~Child()
➋如果函数MyAwesomeFunction
使用QSharedPointer
而不是原生指针作参数呢?
void MyAwesomeFunction(const QSharedPointer<Child> &o) {
std::cout << "Using Object!" << std::endl;
}
int main() {
Parent *const parent = new Parent(0);
QSharedPointer<Child> child(new Child(parent));
MyAwesomeFunction(child);
delete parent;
}
可能的打印输出如下:
Parent()
Child()
Using Object!
~Parent()
~Child()
QObject: shared QObject was deleted directly. The program is malformed and may crash.
在最糟糕的情况下,它会崩溃。问题是,为什么会出现这种问题?这归结于引用计数和谁在计数。我们在这里有一个由父对象维护的引用计数和一个由QSharedPointer
维护的引用计数,它们肯定会在何时删除对象上有分歧。这会导致双重释放和使用后释放错误。不好。但事情比这更复杂!Qt内存管理尽最大努力让您进行手动内存管理,同时仍然维护其自己的系统。因此,如果您在删除父对象之前删除子对象,则可以这样做。子对象的析构函数将从其父对象的子对象列表中注销自己。因此,以下代码是良好的:
void MyAwesomeFunction(Child *o) {
std::cout << "Using Object!" << std::endl;
}
int main() {
Parent *const parent = new Parent(0);
Child *const child = new Child(parent);
MyAwesomeFunction(child);
delete child;
delete parent;
}
以上至少让我们基本了解了有什么问题,它涉及到一个QSharedPointer
。这是一个开始。解决问题的最简单方法是简单地不混合使用两种内存管理方案。如果需要QSharedPointer
,请不要设置parent
。像这样:
void MyAwesomeFunction(const QSharedPointer<Child> &o) {
std::cout << "Using Object!" << std::endl;
}
int main() {
Parent *const parent = new Parent(0);
QSharedPointer<Child> child(new Child());
MyAwesomeFunction(child);
delete parent;
}
正确的输出:
Parent()
Child()
Using Object!
~Parent()
~Child()
QSharedPointer·自定义删除器·构造函数:
template <typename X, typename Deleter>
QSharedPointer::QSharedPointer(X *ptr, Deleter d)
参数 d 指定了此对象的自定义删除器。当强引用计数降至 0
时,将调用自定义删除器而不是operator delete()
。
static void doDeleteLater(MyObject *obj) {
obj->deleteLater();
}
void otherFunction() {
QSharedPointer<MyObject> obj =
QSharedPointer<MyObject>(new MyObject, doDeleteLater);
/* continue using obj */
obj.clear();// calls obj->deleteLater();
}
也可以直接使用成员函数:
QSharedPointer<MyObject> obj =
QSharedPointer<MyObject>(new MyObject, &QObject::deleteLater);
三、QScopedPointer
template <typename T, typename Cleanup>
class QScopedPointer
详细描述
手动管理堆分配的对象既困难又容易出错,常见的结果是代码泄漏内存,难以维护。QScopedPointer
是一个小型实用程序类,它通过将基于堆栈的内存所有权分配给堆分配(通常称为资源获取即初始化(resource acquisition is initialization:RAII)来大大简化这一过程。
QScopedPointer
保证当当前作用域消失时,所指向的对象将被删除。
考虑如一下这个函数,它执行堆分配,并具有各种退出点:
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;
}
这段代码被手动删除调用所拖累。使用QScopedPointer
,代码可以简化为:
void myFunction(bool useSubClass) {
// assuming that MyClass has a virtual destructor
QScopedPointer<MyClass> p(useSubClass ? new MyClass() : new MySubClass);
QScopedPointer<QIODevice> device(handsOverOwnership());
if (m_value > 3)
return;
process(device);
}
编译器为QScopedPointer
生成的代码与手动编写时的代码相同。使用delete
的代码是QScopedPointer
使用的候选代码(如果不是,可能是另一种类型的智能指针,如QSharedPointer
)。QScopedPointer
故意没有复制构造函数或赋值运算符,这样所有权和生存期就可以清楚地交流了。
常规C++指针上的const
限定也可以用QScopedPointer
表示:
const QWidget *const p = new QWidget();
// is equivalent to:
const QScopedPointer<const QWidget> p(new QWidget());
QWidget *const p = new QWidget();
// is equivalent to:
const QScopedPointer<QWidget> p(new QWidget());
const QWidget *p = new QWidget();
// is equivalent to:
QScopedPointer<const QWidget> p(new QWidget());
Custom Cleanup Handlers(自定义‘删除处理器’)
Arrays as well as pointers that have been allocated with malloc
must not be deleted using delete
. QScopedPointer
's second template parameter can be used for custom cleanup handlers.
The following custom cleanup handlers exist:
-
QScopedPointerDeleter - the default, deletes the pointer using
delete
-
QScopedPointerArrayDeleter - deletes the pointer using
delete []
. Use this handler for pointers that were allocated withnew []
. -
QScopedPointerPodDeleter - deletes the pointer using
free()
. Use this handler for pointers that were allocated withmalloc()
. -
QScopedPointerDeleteLater - deletes a pointer by calling
deleteLater()
on it. Use this handler for pointers to QObject's that are actively participating in a QEventLoop.
You can pass your own classes as handlers, provided that they have a public static function static void cleanup(T *pointer)
:
// this QScopedPointer deletes its data using the delete[] operator:
QScopedPointer<int, QScopedPointerArrayDeleter<int> > arrayPointer(new int[42]);
// this QScopedPointer frees its data using free():
QScopedPointer<int, QScopedPointerPodDeleter> podPointer(reinterpret_cast<int *>(malloc(42)));
// this struct calls "myCustomDeallocator" to delete the pointer
struct ScopedPointerCustomDeleter {
static inline void cleanup(MyCustomClass *pointer) {
myCustomDeallocator(pointer);
}
};
// QScopedPointer using a custom deleter:
QScopedPointer<MyCustomClass, ScopedPointerCustomDeleter> customPointer(new MyCustomClass);
Member Function Documentation
T* QScopedPointer::data()/get() const
Returns the value of the pointer referenced by this object. QScopedPointer still owns the object pointed to.
void QScopedPointer::reset(T* other = nullptr)
Deletes the existing object it is pointing to (if any), and sets its pointer to other. QScopedPointer now owns other and will delete it in its destructor.
网友评论