Qt中的智能指针

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wyy626562203/article/details/82422624

Qt中的智能指针

Qt 5.10.1

分别对应boost库,Qt库,c++11的智能指针

boost::scoped_ptr  QScopedPointer unique_ptr 在其生命期结束后会自动删除它所指的对象(确定无需共享)
boost::shared_ptr QSharedPointer shared_ptr 引用计数为0的时候自动删除它所指对象(需要共享)
boost::weak_ptr QWeakPointer weak_ptr 用来避免循环引用(需要访问shared_ptr,又不想改变引用计数)

QAtomicPointer

1.详细说明

QAtomicPointer类是一个模板类,它为指针提供与平台无关的原子操作。

有关整数的原子操作,请参阅QAtomicInteger类。

原子操作是一种复杂的操作,可以不被中断的完成。 QAtomicPointer类为指针提供原子test-and-setfetch-and-store以及 fetch-and-add

2.原子API

2.1 内存序

QAtomicPointer提供了原子test-and-set,fetch-and-store, fetch-and-add功能的几种实现。 每个实现定义了一种内存序语义,描述了处理器如何执行围绕原子指令的内存访问。 由于许多现代体系结构允许无序执行和内存序,因此必须使用正确的语义来确保应用程序在所有处理器上正常运行。

  • Relaxed - 未指定内存序,让编译器和处理器自由重新排序内存访问。
    • Acquire - 原子操作之后的内存访问(按程序顺序)可能不会在原子操作之前重新排序。
    • Release - 原子操作之前的内存访问(按程序顺序)可能不会在原子操作之后重新排序。
    • Ordered - Acquire和Release语义相结合。

2.2 Test-and-set

如果QAtomicPointer的当前值是预期值,则test-and-set函数为QAtomicPointer分配一个新值并返回true。 如果值不相同,则这些函数不执行任何操作并返回false。 此操作等同于以下代码:

if (currentValue == expectedValue) 
{
    currentValue = newValue;
    return true;
}
return false;

有4个测试和设置函数:testAndSetRelaxed()testAndSetAcquire()testAndSetRelease()testAndSetOrdered()。 有关不同内存排序语义的说明,请参见上文。

2.3 Fetch-and-store

原子fetch-and-store函数读取QAtomicPointer的当前值,然后分配一个新值,返回原始值。 此操作等同于以下代码:

T *originalValue = currentValue;
currentValue = newValue;
return originalValue;

有4个fetch-and-store函数:fetchAndStoreRelaxed()fetchAndStoreAcquire()fetchAndStoreRelease()fetchAndStoreOrdered()。 有关不同内存排序语义的说明,请参见上文。

2.4 Fetch-and-add

原子fetch-and-add函数读取QAtomicPointer的当前值,然后将给定值添加到当前值,返回原始值。 此操作等同于以下代码:

T *originalValue = currentValue;
currentValue += valueToAdd;
return originalValue;

有4个fetch-and-add函数:fetchAndAddRelaxed()fetchAndAddAcquire()fetchAndAddRelease()fetchAndAddOrdered()。 有关不同内存排序语义的说明,请参见上文。

2.5 Atomic API的功能测试

提供适用于所有处理器的独立于平台的原子API是非常具有挑战性的。 QAtomicPointer提供的API保证在所有处理器上以原子方式工作。 但是,由于并非所有处理器都支持QAtomicPointer提供的每个操作,因此有必要公开有关处理器的信息。

您可以在编译时使用各种宏检查硬件支持哪些功能。 这些将告诉您硬件是否支持特定操作。 宏的格式为Q_ATOMIC_POINTER_OPERATION_IS_HOW_NATIVEOPERATIONTEST_AND_SETFETCH_AND_STOREFETCH_AND_ADD之一,HOW是ALWAYSSOMETIMESNOT之一。 每个操作都只有一个对应的宏。 例如,如果定义了Q_ATOMIC_POINTER_TEST_AND_SET_IS_ALWAYS_NATIVE,则不定义Q_ATOMIC_POINTER_TEST_AND_SET_IS_SOMETIMES_NATIVEQ_ATOMIC_POINTER_TEST_AND_SET_IS_NOT_NATIVE

等待恒定时间内完成的操作。使用任何类型的锁或循环都不会实现此类操作。对于无需等待的原子操作,除了Q_ATOMIC_POINTER_OPERATION_IS_ALWAYS_NATIVE之外,Qt还定义了Q_ATOMIC_POINTER_OPERATION_IS_WAIT_FREE

如果只在新一代处理器中支持原子操作,QAtomicPointer还提供了一种方法,可以在运行时检查硬件支持的isTestAndSetNative()isFetchAndStoreNative()isFetchAndAddNative()函数。 可以使用isTestAndSetWaitFree()isFetchAndStoreWaitFree()isFetchAndAddWaitFree()函数检测无等待实现。

以下是QAtomicPointer的所有功能宏的完整列表:

QExplicitlySharedDataPointer

1.详细说明

QExplicitlySharedDataPointer类表示指向显式共享对象的指针。

QExplicitlySharedDataPointer <T>使您自己编写自己的显式共享类变得容易。 QExplicitlySharedDataPointer实现线程安全引用计数,确保将QExplicitlySharedDataPointers添加到您的可重入类而不会使它们不可重入。

除了一个很大的区别,QExplicitlySharedDataPointer类似QSharedDataPointer。最大的区别是QExplicitlySharedDataPointer的成员函数在允许修改共享数据对象之前,不对QSharedDataPointer的非const成员执行的写操作(detach())进行自动复制。有一个detach()函数可用,但是如果你真的想要detach(),你必须自己调用它。这意味着QExplicitlySharedDataPointers的行为类似于常规C++指针,除了共享数据对象引用计数为0将被释放,它们避免了悬空指针问题。

通过示例比较QExplicitlySharedDataPointerQSharedDataPointer是有益的。考虑QSharedDataPointer中的Employee示例,修改为使用显式共享,如讨论隐式与显式共享中所述。

请注意,如果您使用此类但发现您正在调用detach(),则可能应该使用QSharedDataPointer。

在成员函数文档中,d指针始终引用指向共享数据对象的内部指针。

QFunctionPointer

这是void(*)()的typedef,指向不带参数且返回void的函数的指针。

QPointer

1.详细说明

QPointer类是一个模板类,它为QObject提供了保护指针。

保护指针QPointer 的行为类似于普通的C++指针T *,不同之处在于它在被引用的对象被销毁时自动设置为0(与普通的C++指针不同,在这种情况下它变为“悬空指针”)。 T必须是QObject的子类。

当您需要存储指向其他人拥有的QObject的指针时,保护指针很有用,因此在您仍然保持对它的引用时可能会被销毁。 您可以安全地测试指针的有效性。

请注意,Qt 5在使用QPointer时需要轻微的更改。

  • 当在QWidget(或QWidget的子类)上使用QPointer时,之前QPointer将在QWidget析构函数中释放。 现在,QPointer被QObject析构函数释放(因为这是清除QWeakPointer对象的时候)。 在QWidget析构函数销毁要跟踪的Widget的子对象之前,不会清除跟踪Widget的任何QPointers。

Qt还提供QSharedPointer,它是引用计数共享指针对象的实现,可用于维护对单个指针的引用集合。

例如:

QPointer<QLabel> label = new QLabel;
label->setText("&Status:");
...
    if (label)
        label->show();

如果在此期间删除QLabel,则label变量将保持0而不是非法地址,并且永远不会执行最后一行。

除了指针算术运算符(+, - ,++和 - )之外,QPointer可用的函数和运算符与普通无保护指针可用的函数和运算符相同,它们通常仅用于对象数组。像普通指针一样使用QPointers,您不需要阅读此类文档。

对于创建受保护的指针,您可以从T *或相同类型的另一个受保护指针构造或分配它们。您可以使用operator ==()operator!=()来比较它们,或者使用isNull()测试是否为0。您可以使用* xx->成员表示法取消引用它们。

一个有保护的指针将自动转换为T *,因此您可以自由地混合有保护和无保护的指针。这意味着如果你有一个QPointer <QWidget>,你可以将它传递给一个需要QWidget *的函数。因此,声明函数将QPointer作为参数是没有什么价值的;只需使用普通指针。需要存储指针时使用QPointer。

请注意,类T必须继承QObject,否则将导致编译或链接错误。

QScopedArrayPointer

1.详细说明

QScopedArrayPointer类存储指向动态分配的对象数组的指针,并在销毁时删除它。

QScopedArrayPointer是一个QScopedPointer,默认使用delete []运算符删除它指向的对象。 它还具有operator []方便使用,因此我们可以编写:

void foo()
{
    QScopedArrayPointer<int> i(new int[10]);
    i[2] = 42;
    ...
    return; // our integer array is now deleted using delete[]
}

QScopedPointer

1.详细说明

QScopedPointer类存储指向动态分配对象的指针,并在销毁时删除它。

手动管理堆分配的对象很难并且容易出错,常见的结果是泄漏内存并且难以维护。 QScopedPointer是一个小型实用类,通过将基于栈的内存所有权分配给堆来大大简化这一过程,通常称为资源获取就是初始化(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());

2.自定义清理处理程序

不得使用delete删除数组以及已分配malloc的指针。 QScopedPointer的第二个模板参数可用于自定义清理处理程序。

存在以下自定义清理处理程序:

  • QScopedPointerDeleter - 默认值,使用delete删除指针
  • QScopedPointerArrayDeleter - 使用delete []删除指针。 将此类用于使用new []分配的指针。
  • QScopedPointerPodDeleter - 使用free()删除指针。 将此类用于使用malloc()分配的指针。
  • QScopedPointerDeleteLater - 通过调用deleteLater()来删除指针。 使用此类指向已激活QEventLoop的QObject。

您可以将自己的类作为处理程序传递,前提是它们具有公共静态函数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);

3.预声明的指针

预声明的类可以在QScopedPointer中使用,只要预声明的类的析构函数在QScopedPointer需要清理时可用即可。

具体来说,这意味着包含指向预声明类的QScopedPointer的所有类必须具有非内联构造函数,析构函数和赋值运算符:

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.
};

否则,编译器会输出关于无法析构MyPrivateClass的警告。

QSharedDataPointer

1.详细说明

QSharedDataPointer类表示指向隐式共享对象的指针。

QSharedDataPointer <T>使您自己编写自己的隐式共享类变得容易。 QSharedDataPointer实现线程安全引用计数,确保将QSharedDataPointers添加到您的可重入类而不会使它们不可重入。

许多Qt类使用隐式共享来将指针的速度和内存效率与类的易用性结合起来。 有关更多信息,请参阅“共享类”页面。

假设您要隐式共享Employee类。 程序是:

  • 将类Employee定义为具有QSharedDataPointer <EmployeeData>类型的单个数据成员。
  • 定义从QSharedData派生的EmployeeData类,所有数据成员通常放在Employee类中。

为了在实践中显示这一点,我们检查了隐式共享的Employee类的源代码。 在头文件中,我们定义了两个类Employee和EmployeeData。

#include <QSharedData>
#include <QString>

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; }
    Employee(int id, const QString &name) {
        d = new EmployeeData;
        setId(id);
        setName(name);
    }
    Employee(const Employee &other)
        : d (other.d)
        {
        }
    void setId(int id) { d->id = id; }
    void setName(const QString &name) { d->name = name; }

    int id() const { return d->id; }
    QString name() const { return d->name; }

    private:
    QSharedDataPointer<EmployeeData> d;
};

在Employee类中,请注意单个数据成员,类型为QSharedDataPointer 的d指针。 员工数据的所有访问都必须通过d指针的运算符- >()。 对于写访问,operator - >()将自动调用detach(),如果共享数据对象的引用计数大于1,则会创建共享数据对象的副本。这可确保对一个Employee对象的写入不会影响任何共享相同EmployeeData对象的其他Employee对象。

EmployeeData类继承QSharedData,后者提供幕后引用计数器。 EmployeeData有一个默认构造函数,一个复制构造函数和一个析构函数。 通常,这些的简单实现是隐式共享类的数据类中所需要的。

为Employee类实现两个构造函数也很简单。 两者都创建一个EmployeeData的新实例并将其分配给d指针。

Employee() { d = new EmployeeData; }

Employee(int id, const QString &name) {
    d = new EmployeeData;
    setId(id);
    setName(name);
}

请注意,类Employee也定义了一个简单的复制构造函数,在这种情况下并不是严格要求的。

Employee(const Employee &other): d (other.d)
{
}

此处不严格要求复制构造函数,因为类EmployeeData与Employee(employee.h)类包含在同一文件中。 但是,将QSharedData的私有子类包含在与包含QSharedDataPointer的公共类相同的文件中并不常见。 通常,我们的想法是通过将QSharedData的私有子类放在一个不包含在公共文件中的单独文件中来隐藏它的私有子类。 在这种情况下,我们通常会将类EmployeeData放在一个单独的文件中,该文件不会包含在employee.h中。 相反,我们只需这样在employee.h中预先声明私有子类EmployeeData:

class EmployeeData;

如果我们在这里这样做了,那么显示的复制构造函数将是必需的。 由于复制构造函数很简单,因此您也可以始终包含它。

在幕后,只要将Employee对象复制,分配或作为参数传递,QSharedDataPointer就会自动增加引用计数。 每当删除Employee对象或超出范围时,它会减少引用计数。 如果引用计数达到0,则会自动删除共享的EmployeeData对象。

在Employee的非const成员函数中,每当d指针被解除引用时,QSharedDataPointer会自动调用detach()以确保该函数在其自己的数据副本上运行。

void setId(int id) { d->id = id; }
void setName(const QString &name) { d->name = name; }

请注意,如果由于多次取消引用d指针而在成员函数中多次调用detach(),则detach()将仅在第一次调用时创建共享数据的副本(如果有的话),因为在第二次和后续调用detach(),引用计数将再次变为1。

但请注意,在第二个Employee构造函数中,它接受一个employee ID和一个name,同时调用setId()和setName(),但它们不会导致写入时复制,因为新构造的EmployeeData对象的引用计数刚被设置为1。

在Employee的const成员函数中,取消引用d指针不会导致调用detach()。

int id() const { return d->id; }
QString name() const { return d->name; }

请注意,不需要为Employee类实现复制构造函数或赋值运算符,因为C++编译器提供的复制构造函数和赋值运算符将按成员浅层复制所需的成员。 唯一要复制的成员是d指针,它是一个QSharedDataPointer,其operator =()只增加共享EmployeeData对象的引用计数。

2.隐式与显式共享

隐式共享可能不适合Employee类。 一个创建两个Employee隐式共享实例的简单示例。

#include "employee.h"

int main()
{
    Employee e1(1001, "Albrecht Durer");
    Employee e2 = e1;
    e1.setName("Hans Holbein");
}

创建第二个员工e2并为其分配e1后,e1和e2都引用Albrecht Durer,员工1001.两个Employee对象都指向EmployeeData的相同实例,其引用数为2.然后e1.setName(“Hans” 调用Holbein“)来更改员工姓名,但由于引用计数大于1,因此在更改名称之前会执行写入时的副本。 现在e1和e2指向不同的EmployeeData对象。 它们有不同的名称,但都有ID 1001,这可能不是你想要的。 当然,您可以继续使用e1.setId(1002),如果您真的想要创建第二个独特的员工,但如果您只想在任何地方更改员工的名称,请考虑在Employee类中使用显式共享而不是 隐性共享。

如果将Employee类中的d指针声明为QExplicitlySharedDataPointer <EmployeeData>,则使用显式共享,并且不会自动执行写操作上的复制(即,在非const函数中不调用detach())。 在这种情况下,在e1.setName(“Hans Holbein”)之后,员工的姓名已经更改,但是e1和e2仍然引用了EmployeeData的相同实例,因此只有一个员工的ID为1001。

在成员函数文档中,d指针始终引用指向共享数据对象的内部指针。

3.优化Qt容器中的使用性能

如果它类似于上面的Employee类,并且使用QSharedDataPointer或QExplicitlySharedDataPointer作为唯一成员,则应考虑使用Q_DECLARE_TYPEINFO()宏将隐式共享类标记为可移动类型。 使用Qt的容器类时,这可以提高性能和内存效率。

QSharedPointer

1.详细说明

QSharedPointer类包含对共享指针的强引用

QSharedPointer是C++中的自动共享指针。 它的行为与普通指针完全相同,包括对constness的关注。

如果没有其他QSharedPointer对象引用它,QSharedPointer将删除它超出范围时所持有的指针。

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

2.线程安全

QSharedPointerQWeakPointer是线程安全的,并且在指针值上以原子方式运行。 不同的线程也可以同时访问指向同一对象的QSharedPointer或QWeakPointer,而无需加锁。

应该注意,虽然可以以这种方式访问指针值,但QSharedPointerQWeakPointer不能保证被指向的对象。 该对象的线程安全和重入规则仍然适用。

3.其他指针类

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

QSharedPointer通过外部引用计数(即放置在对象外部的引用计数器)保存共享指针。 与其名称一样,指针值在QSharedPointerQWeakPointer的所有实例之间共享。 指针指向的对象的内容不应被视为共享,但是:只有一个对象。 因此,QSharedPointer不提供分离或复制指向对象的方法。

另一方面,QSharedDataPointer保存指向共享数据的指针(即,从QSharedData派生的类)。它通过内部引用计数来实现,它放在QSharedData基类中。因此,该类可以根据对被保护数据的访问类型进行分离:如果它是非const访问,则会以原子方式创建一个副本以完成操作。

QExplicitlySharedDataPointerQSharedDataPointer的变体,它只在显式调用QExplicitlySharedDataPointer::detach()时才会分离(因此得此名称)。

QScopedPointer只是保存一个指向堆分配对象的指针,并在其析构函数中删除它。当对象需要堆分配和删除时,此类很有用,但不再需要。 QScopedPointer是轻量级的,它不使用额外的结构或引用计数。

最后,QPointer保存一个指向QObject派生对象的指针,但它的作用很弱。 QWeakPointer具有相同的功能,但不推荐使用该功能。

4.可选的指针跟踪

QSharedPointer的一个特性是可以在编译时启用以进行调试,它是一种指针跟踪机制。 启用后,QSharedPointer在全局集中注册它跟踪的所有指针。 这允许人们捕获错误,例如将相同的指针分配给两个QSharedPointer对象。

通过在包含QSharedPointer标头之前定义QT_SHAREDPOINTER_TRACK_POINTERS宏来启用此功能。

即使编译没有该功能的代码,也可以安全地使用此功能。 QSharedPointer将确保指针从跟踪器中删除,即使是在没有指针跟踪的情况下编译的代码也是如此。

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

QWaylandPointer

1.详细说明

QWaylandPointer类表示指针设备。

此类提供对QWaylandSeat中指针设备的访问。 它对应于Wayland界面wl_pointer。

QWeakPointer

1.详细说明

QWeakPointer类包含对共享指针的弱引用

QWeakPointer是对C++中指针的自动弱引用。它不能用于直接取消引用指针,但它可用于验证指针是否已在另一个上下文中被删除。

QWeakPointer对象只能通过QSharedPointer的赋值来创建。

值得注意的是,QWeakPointer不提供自动转换操作符来防止错误发生。即使QWeakPointer跟踪指针,也不应将其视为指针本身,因为它不能保证指向的对象保持有效。

因此,要访问QWeakPointer正在跟踪的指针,必须首先将其提升为QSharedPointer并验证结果对象是否为null。 QSharedPointer保证不删除对象,因此如果获得非null对象,则可以使用指针。有关示例,请参阅QWeakPointer::toStrongRef()

QWeakPointer还提供QWeakPointer::data()方法,该方法返回跟踪指针,而不确保它仍然有效。如果您可以通过外部方式保证不会删除对象(或者您只需要指针值)并且使用toStrongRef()创建QSharedPointer的成本太高,则会提供此函数。

猜你喜欢

转载自blog.csdn.net/wyy626562203/article/details/82422624