QPointer

QPointer 使一种受保护的指针,当其引用的对象被销毁时,它会被自动清除(但是,销毁引用对象还是必须手动delete)。QPointer所指向的对象必须是QObject或其派生类对象。

当多个指针指向同一个 Object 对象时,引用的对象可能被释放掉,这时使用 QPointer 就可以安全的测试引用对象是否有效,防止发生指针空悬。

注意:Qt5 之前,QPointer 指向一个 QWidget 对象(或子类对象)时,QPointer 由 QWidget 的析构函数清除,Qt5 之后 由 QObject 的析构函数清除。在析构函数销毁被跟踪 QWidget 的子项之前,任何跟踪 QWidget 的 QPointers 都不会被清除。

QPointer 提供的函数和运算符与普通指针的函数和运算符相同,但算术运算符+、-、 ++ 和 --除外(它们通常仅用于对象数组)。

创建 QPointer 指针,可以使用构造函数、用 T * 赋值或相同类型的其他 QPointer 。QPointer 比较可以使用 == 和 !=,或使用 isNull() 进行测试。可以使用 nullptr 或 *xx->member 取消引用。

QPointer 和普通指针可以混用,QPointer会自动转换为指针*。可以把 QPointer 对象传递给需要 QWidget * 参数的函数。因此,声明函数时没有必要用 QPointer 作为参数,只需使用普通指针即可。

#include <QCoreApplication>
#include <QTimer>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QTimer *timer = new QTimer;
// QPointer<QTimer> timer = new QTimer;
delete timer;
// 不使用QPointer时需要设置timer为NULL
// timer=NULL;
if(timer)
{
qInfo()<<"timer is not null";
}
else
{
qInfo()<<"timer is null";
}
return a.exec();
}

不使用QPointer时,输出“Label is not null”,原因是,delete后未置空,易造成指针悬空。使用 QPointer 时,输出"timer is null"。

在QPointer中 .是指针的属性,-> 是对象的属性。

QScopedPointer

QScopedPointer 类似于 C++ 11 中的 unique_ptr 用于管理动态分配的对象的独占所有权,即同一时间只能有一个QScopedPointer指向该对象。

QScopedPointer使用了RAII(资源获取即初始化)技术,当QScopedPointer被销毁时,它将自动释放所管理的对象的内存。QScopedPointer不支持拷贝和赋值操作,这是为了避免在多个指针之间共享所有权的问题。如果需要在多个指针之间转移所有权,应该使用QSharedPointer或QWeakPointer。

手动管理堆中分配的对象非常困难而且容易出错,容易导致内存泄露。QScopedPointer是一个简化内存管理的工具类,它通过将堆分配的空间赋值给基于栈的内存,通常称为RAII(resource acquisition is initialization)。

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的选项之一。QScopedPointer没有赋值构造函数或赋值操作,以便清楚的表达所有权和生命周期。

C++ 中const修饰的指针也可以使用QScopedPointer来表示。

    const QWidget *const p = new QWidget();
// 等价于
const QScopedPointer<const QWidget> p(new QWidget()); QWidget *const p = new QWidget();
// 等价于
const QScopedPointer<QWidget> p(new QWidget()); const QWidget *p = new QWidget();
// 等价于
QScopedPointer<const QWidget> p(new QWidget());

自定义清理处理器:

使用malloc分配的数组指针不能使用delete删除,QScopedPointer的第二个模板参数可以用于自定义清理处理器。QT提供了以下自定义清理处理器:

  • QScopedPointerDeleter 默认处理器,使用delete删除指针。
  • QScopedPointerArrayDeleter 使用delete []删除。用于处理使用new [] 创建的指针。
  • QScopedPointerPodDeleter 使用free()删除指针。用于处理使用malloc()创建的指针。
  • QScopedPointerDeleteLater 使用deleteLater()删除指针。用于处理QEventLoop中使用的QObject指针。

    自定义清理处理器中必须提供一个public的静态函数:public static function void cleanup(T *pointer)
 // QScopedPointer 使用 delete[] 删除数据
QScopedPointer<int, QScopedPointerArrayDeleter<int> > arrayPointer(new int[42]); // QScopedPointer 使用 free()释放内存
QScopedPointer<int, QScopedPointerPodDeleter> podPointer(reinterpret_cast<int *>(malloc(42))); // 该 struct 调用 "myCustomDeallocator" 删除指针
struct ScopedPointerCustomDeleter
{
static inline void cleanup(MyCustomClass *pointer)
{
myCustomDeallocator(pointer);
}
}; // QScopedPointer 使用自定义清理处理器:
QScopedPointer<MyCustomClass, ScopedPointerCustomDeleter> customPointer(new MyCustomClass);

QScopedPointer可以使用前置声明的类,但是要保证QScopedPointer需要清理的时候前置声明的类的析构函数可用。否则,编译器将输出析构函数不可用的警告。

 class MyPrivateClass; // 前置声明 MyPrivateClass

class MyClass
{
private:
QScopedPointer<MyPrivateClass> privatePtr; // QScopedPointer 使用前置声明类 public:
MyClass(); // OK
inline ~MyClass() {} // VIOLATION - 析构函数不能使用inline修饰 private:
Q_DISABLE_COPY(MyClass) // OK - 设置拷贝构造函数和赋值操作不可用,编译器不会自动生成构造函数和赋值操作
};

成员函数

QScopedPointer::QScopedPointer(T *p = nullptr)

构造 QScopedPointer 实例,并指向p

QScopedPointer::~QScopedPointer()

销毁QScopedPointer对象。删除它指向的对象

T *QScopedPointer::data() const

返回QScopedPointer指向对象的值。QScopedPointer仍然指向该对象

T *QScopedPointer::get() const

与 data()功能相同

bool QScopedPointer::isNull() const

QScopedPointer指向的对象为nullptr时返回true

void QScopedPointer::reset(T *other = nullptr)

删除已指向的对象并指向新对象other

QScopedArrayPointer

QScopedArrayPointer 与 QScopedPointer 类似,但是删除指针时使用的时 delete[] 操作。QScopedArrayPointer 存储的指针指向动态分配的数组对象。如果我们指向的内存数据是一个数组,这时可以用 QScopedArrayPointer。例如:

 void foo()
{
QScopedArrayPointer<int> i(new int[10]);
i[2] = 42;
...
return; // 此时我们定义的 integer 数组会使用 delete[] 自动删除
}

QSharedPointer

QSharedPointer 相当于C++11 标准中的 shared_ptr, 用于管理动态分配的对象的共享所有权,即多个 QSharedPointer 对象可以指向同一个对象,并共享该对象的内存管理。它使用引用计数来追踪对象的使用情况,当最后一个 QSharedPointer 对象被销毁时,它将自动删除它所持有的指针。由于使用了引用计数,QSharedPointer 能够自动处理指针的生命周期,避免内存泄漏和空悬指针等问题,因此是Qt中最常用的智能指针。

需要注意的是,QSharedPointer只能管理动态分配的对象的内存。如果我们将其用于指向栈对象或全局对象,那么它就不会自动释放对象的内存,这可能会导致程序崩溃或内存泄漏。

创建 QSharedPointer 对象可以用普通指针、另一个 QSharedPointer 对象,也可以通过将 QWeakPointer 对象提升为强引用来创建。

线程安全

QSharedPointer 和 QWeekPointer 是可重入类,如果不进行同步,多个线程无法同时访问指定的 QSharedPointer 对象或 QWeakPointer 对象。

多个 QSharedPointer 和 QWeekPointer 指向同一个对象的情况下,多个线程可以安全的访问这些 QSharedPointer 和 QWeekPointer对象。QSharedPointer 采用的引用计数机制是原子操作,不需要手动同步。但是要注意,QSharedPointer 和 QWeekPointer 所指向的对象不一定是线程安全的,需要采用线程安全和重入规则来保证 QSharedPointer 和 QWeekPointer 所指向对象的安全。

其他指针类

Qt还提供了另外两个指针包装类: QPointer 和 QSharedDataPointer。它们彼此不兼容,因为每个都有其非常不同的用例。

QSharedPointer 通过外部引用计数(即放置在对象外部的引用计数器)持有共享指针,指针值在 QSharedPointer 和 QWeekPointer 的所有实例之间共享。但是,指针指向的对象不应被视为共享的:都是同一个对象。QSharedPointer 不提供detach(隐式共享)或拷贝所引用对象的方法。

QSharedDataPointer 通过基类 QSharedData 内的引用计数持有共享数据的指针(共享数据派生自 QSharedData 类)。QSharedDataPointer 可以根据访问类型对受保护数据执行detach(隐式共享):如果不是读访问,则以原子方式创建一个副本以完成操作。

QExplicitlySharedDataPointer 是 QSharedDataPointer 的一个变量,QSharedDataPointer只有在 QExplicitlySharedDataPointer::detach() 时才会执行detach。

QScopedPointer 专为堆内分配、删除对象设计,它持有指向堆分配对象的指针,并在其析构函数中删除对象。 QScopedPointer 是轻量级的,它不使用额外的结构或引用计数。

QPointer 用于持有 QObject 派生对象的指针,但是一种弱引用。QWeakPointer 具有相同的功能,但不推荐使用该功能。

可选指针跟踪

编译调试时可以开启 QSharedPointer 的指针跟踪功能。启用后,QSharedPointer 会在全局集合中注册它跟踪的所有指针。这样就可以捕获错误(例如:将同一指针分配给两个 QSharedPointer 对象)。

开启指针跟踪功能需要在 include QSharedPointer 前定义宏QT_SHAREDPOINTER_TRACK_POINTERS。

即使没有启用指针跟踪功能进行代码编译,使用 QSharedPointer 跟踪指针也是安全的。如果编译的代码没有开启指针跟踪功能,QSharedPointer 会从跟踪器中删除指针。

注意,指针跟踪功能对多重继承或虚拟继承有限制(此时两个不同的指针可以引用同一对象)。在这种情况下,如果指针被强制转换为不同的类型并且其值发生更改,则 QSharedPointer 的指针跟踪机制可能无法检测到被跟踪的对象是否为同一对象。

QWeakPointer

QWeakPointer 是对指针的弱引用,相当于C++11 标准中的 weak_ptr。QWeakPointer 不影响指针引用计数,可以用于验证指针是否在已另一个上下文中被删除。QWeakPointer对象的创建只能通过QSharedPointer赋值。QWeakPointer 用于追踪指针,但并不代表指针本身,它不保证指针对象的有效性,也不提供转换操作。如果要访问 QWeakPointer 追踪的指针,需要向将其提升为 QSharedPointer 并验证该对象是否为 null,若对象不为 null,则可以使用该指针。QWeakPointer::toStrongRef()用于将 QWeakPointer 转换为 QSharedPointer。

QWeakPointer 指向 QSharedPointer 所管理的对象,但不会增加对象的引用计数,也不会影响对象的生命周期。当对象被释放时,QWeakPointer会自动被置为空指针,避免了空悬指针的问题。

#include <QSharedPointer>
#include <QWeakPointer>
#include <QDebug> class MyClass
{
public:
MyClass(int value) : m_value(value) {
qDebug() << "MyClass constructor called with value" << m_value;
}
~MyClass() {
qDebug() << "MyClass destructor called with value" << m_value;
}
int getValue() const {
return m_value;
} private:
int m_value;
}; int main()
{
QSharedPointer<MyClass> shared(new MyClass(20));
QWeakPointer<MyClass> weak(shared); qDebug() << "Shared pointer value:" << shared->getValue();
qDebug() << "Weak pointer value:" << weak.data()->getValue(); shared.clear();
// 此时,MyClass对象的引用计数为0,将被自动删除,而此时 QWeakPointer 对象 weak 也为null。 if (weak.isNull()) {
qDebug() << "Weak pointer is null - object has been deleted"; //执行
} else {
qDebug() << "Weak pointer is not null - object still exists";
} return 0;
}

QSharedDataPointer

QSharedDataPointer 用于简化隐式共享类的实现。QSharedDataPointer 实现了线程安全的引用计数,保证了向可重入类添加 QSharedDataPointers 时不破坏的的可重入性。Qt 中很多类都采用了隐式共享来提升指针的访问速度和内存使用效率。Qt 的容器类都使用了隐式共享,如 QList 、QVarLengthArray 、QStack 、QQueue、 QSet、 QMap、 QMultiMap、 QHash、 QMultiHash 。

例如,实现 Employee 类的隐式共享,需要以下步骤:

  1. 定义 Employee 类,并声明一个 QSharedDataPointer 成员变量;
  2. 定义一个 EmployeeData 并继承 QSharedData,将 Employee 需要的成员变量在 EmployeeData 中进行声明。
#include <QSharedData>
#include <QString> // EmployeeData 继承自 QSharedData,QSharedData为其提供了一个引用计数器
// EmployeeData 要实现共享必须提供默认的构造函数、拷贝构造函数和析构函数
// 如果要对 Employee 类的使用者隐藏数据,应该把 EmployeeData 类声明到独立的 .h 文件中
class EmployeeData : public QSharedData
{
public:
EmployeeData() : id(-1) { }
// 拷贝构造函数
EmployeeData(const EmployeeData &other)
: QSharedData(other), id(other.id), name(other.name) { }
~EmployeeData() { } // 共享数据
int id;
QString name;
}; class Employee
{
public:
Employee() { d = new EmployeeData; }
// 该构造函数调用了 setId(id) 和 setName(name), 2次修改共享数据,但是并不会触发 copy on write
Employee(int id, const QString &name) {
d = new EmployeeData;
setId(id); // 此时 EmployeeData 的引用计数为 1
setName(name); // 引用计数为 1 ,不会 copy 共享数据
}
// 这个拷贝构造函数可以不用定义,编译器会自动生成拷贝构造函数
// 拷贝构造函数会对成员变量 d 进行赋值,赋值操作 =() 会增加共享数据的引用计数
Employee(const Employee &other)
: d (other.d)
{
}
// 修改数据时,操作符 ->() 会自动调用 detach() ,如果共享数据的引用计数大于1,会自动copy一份数据用于修改。
// 保证一个 Employee 对象对共享数据的修改不会影响其它Employee对象引用的共享数据
void setId(int id) { d->id = id; }
void setName(const QString &name) { d->name = name; } // 不修改 EmployeeData ,操作符 -> 不会触发detach()
int id() const { return d->id; }
QString name() const { return d->name; } private:
// Employee 只用一个成员变量,其它数据在 EmployeeData 中定义
QSharedDataPointer<EmployeeData> d;
};

在上面的场景中,Employee 对象拷贝、赋值或作为参数传递时,QSharedDataPointer 自动增加引用计数。Employee 对象删除或不在当前范围时, QSharedDataPointer 自动减少引用计数。当引用计数为 0 时,EmployeeData 对象自动被删除。当 Employee 的成员函数对成员变量 d 进行修改时,QSharedDataPointer 自动调用 detach() 操作,并为 Employee 对象拷贝出一份数据,保证 Employee 对象对数据的修改不会影响其它对象。如果 Employee 有多个成员函数都会修改成员变量 d,detach() 会被调用多次,但是只有首次调用 detach() 时才会拷贝共享数据,因为第一次调用 detach() 后成员变量 d 指向新拷贝的数据,它的引用计数为 1。

隐式共享需要注意的问题:

看下面的代码,如果 e1 和 e2 都代表 id 为 1001 的数据,那么将出现 1001 有 2 个不同 name 的问题,这在数据一致性上可能会有问题。这种问题需要使用显示共享来解决,即在 Employee 类中声明为显示共享 QExplicitlySharedDataPointer d; 显示共享不会自动执行 copy on write 操作。

#include "employee.h"

int main()
{
Employee e1(1001, "Albrecht Durer");
// e1 和 e2 指向同一个共享数据(1001, "Albrecht Durer")
Employee e2 = e1;
// setName 后成员变量 e1 将指向另一个数据(1001, "Hans Holbein")
e1.setName("Hans Holbein");
}

可以考虑使用宏Q_DECLARE_TYPEINFO()将隐式共享类声明为 movable 类型,例如上面的 Employee 类。

隐式共享的 iterator 问题:当使用 iterator 遍历容器时,不能进行容器拷贝。例如:

QList<int> a, b;
// 构造一个用 0 初始化的 list
a.resize(100000); QList<int>::iterator i = a.begin();
// 下面是 iterator i 的错误使用方式 b = a; // 与 STL 容器不同的是,如果此时执行 *i = 4 会发生隐式拷贝 a[0] = 5;
// 此时 a 已经从共享数据中分离出来,虽然 i 是 容器 a 的遍历器,但实际上是 b 的遍历器,此时 (*i) == 0 b.clear();
// 此时遍历器 i 已经完全无效了 int j = *i;
// 此时会出现 i 未定义的情况 // 如果使用的是 STL 的容器类 std::list<T> 不会发生上述情况,此时 (*i) == 5

QExplicitlySharedDataPointer

QExplicitlySharedDataPointer 用于简化显式共享类的实现。QExplicitlySharedDataPointer 实现了线程安全的引用计数,保证了向可重入类添加 QSharedDataPointers 时不破坏的的可重入性。

QExplicitlySharedDataPointer 与 QSharedDataPointer 类似,惟一不同的是 QExplicitlySharedDataPointer 的成员函数在出现写操作时不会自动 copy 共享数据。QExplicitlySharedDataPointer 的 detach() 方法只能手动调用。QExplicitlySharedDataPointers 自动进行引用计数并在引用计数变为 0 时删除共享数据。

参考文章:

Qt 智能指针介绍: QSharedPointer、QWeakPointer 、QScopedPointer 、QPointer(附实例)

Qt 中的智能指针

【图解】Qt中的智能指针

Qt 智能指针学习(7种QT的特有指针)

QT 智能指针 QPointer QScopedPointer QSharedPointer QWeakPointer QSharedDataPointer 隐式共享 显示共享的更多相关文章

  1. Qt智能指针QPointer, QSharedDataPointer ,QSharedPointer,QWeakPointer和QScopedPointer

    QPointer (4.0) 已经过时,可以被QWeakPointer所替代,它不是线程安全的. QSharedDataPointer (4.0) -- 提供对数据的COPY-ON-WRITE以及浅拷 ...

  2. Qt 智能指针学习(7种指针)

    Qt 智能指针学习 转载自:http://blog.csdn.net/dbzhang800/article/details/6403285 从内存泄露开始? 很简单的入门程序,应该比较熟悉吧 ^_^ ...

  3. Qt 智能指针学习(7种QT的特有指针)

    从内存泄露开始? 很简单的入门程序,应该比较熟悉吧 ^_^ #include <QApplication> #include <QLabel> int main(int arg ...

  4. Qt 智能指针学习(7种QT智能指针和4种std智能指针)

    从内存泄露开始? 很简单的入门程序,应该比较熟悉吧 ^_^ #include <QApplication> #include <QLabel> int main(int arg ...

  5. Qt 智能指针学习

    原地址:http://blog.csdn.net/dbzhang800/article/details/6403285 从内存泄露开始? 很简单的入门程序,应该比较熟悉吧 ^_^ #include & ...

  6. [转]Qt 智能指针学习

    从内存泄露开始? 很简单的入门程序,应该比较熟悉吧 ^_^ #include <QApplication> #include <QLabel> int main(int arg ...

  7. Qt智能指针简明说明

    下面的智能指针分别对应boost库,Qt库,c++11的智能指针 boost::scoped_ptr  QScopedPointer unique_ptr 在其生命期结束后会自动删除它所指的对象(确定 ...

  8. Qt中容器类应该如何存储对象(最好使用对象指针类型,如:QList<TestObj*>,而不要使用 QList<TestObj> 这样的定义,建议采用 智能指针QSharedPointer)

    Qt提供了丰富的容器类型,如:QList.QVector.QMap等等.详细的使用方法可以参考官方文档,网上也有很多示例文章,不过大部分文章的举例都是使用基础类型:如int.QString等.如果我们 ...

  9. 智能指针类模板(中)——Qt中的智能指针

    Qt中的智能指针-QPointer .当其指向的对象被销毁时,它会被自动置空 .析构时不会自动销毁所指向的对象-QSharedPointer .引用计数型智能指针 .可以被自由的拷贝和赋值 .当引用计 ...

  10. C++中的智能指针类模板

    1,智能指针本质上是一个对象,这个对象可以像原生的指针一样使用,因为智能指 针相关的类通过重载的技术将指针相关的操作符都进行了重载,所以智能指针对象可以像原生指针一样操作,今天学习智能指针类模板,通过 ...

随机推荐

  1. Js获取数据类型

    Js获取数据类型 JavaScript有着七种基本类型String.Number.Boolean.Null.Undefined.Symbol.Object,前六种为基本数据类型,Object为引用类型 ...

  2. Java集合框架学习(八) HashMap详解

    HashMap介绍 HashMap是一个基于Map的集合类,用于存储Key和Value的键值对. 通常用HashMap<Key, Value> or HashMap<K, V> ...

  3. 编译 windows 上的 qt 静态库

    记录命令行编译过程: 针对 Qt 5.15.2 版本, 只需要 Source 文件就行 打开 x86 Native Tools Command Prompt for VS 2019,如果需要编译 x6 ...

  4. 【Azure 应用服务】Java ODBC代码中,启用 Managed Identity 登录 SQL Server 报错 Managed Identity authentication is not available

    问题描述 在App Service中启用Identity后,使用系统自动生成 Identity. 使用如下代码连接数据库 SQL Server: SQLServerDataSource dataSou ...

  5. 【Azure 存储服务】.NET7.0 示例代码之上传大文件到Azure Storage Blob (二)

    问题描述 在上一篇博文([Azure 存储服务].NET7.0 示例代码之上传大文件到Azure Storage Blob (一):https://www.cnblogs.com/lulight/p/ ...

  6. 【Azure 应用服务】部署Azure Web App时,是否可以替换hostingstart.html文件呢?

    问题描述 当成功创建一个Web App时,通过高级工具(Kudu)可以查看 Web App的根目录(wwwroot)中有一个默认的文件(hostingstart.html).它就是应用服务的默认页面. ...

  7. 【Azure 微服务】Service fabric升级结构版本失败问题

    问题描述 Service fabric升级结构版本失败,Service Fabric的可靠性层是白银层,持久性层为青铜层,当把节点从6个直接在虚拟规模集(VMSS)中缩放成了3个.从而引起了Servi ...

  8. Hibernate-Validator扩展之自定义注解

    一.Hibernate-Validator介绍 ​ Hibernate-Validator框架提供了一系列的注解去校验字段是否符合预期,如@NotNull注解可以校验字段是否为null,如果为null ...

  9. 图数据库认证考试 NGCP 错题解析 vol.02:这 10 道题竟无一人全部答对

    如果你读过「NebulaGraph 错题解析第一期」,大概知道在错题解析未出来之前,NebulaGraph 专业技能认证 NGCP(全称 NebulaGraph Certified Professio ...

  10. 关闭mysql上锁的表/数据

    一.输入查询语句,查看是否有数据被上锁 select * from information_schema.innodb_trx; 取 trx_mysql_thread_id 字段值 kill < ...