全面解析QSharedPointer

发布时间:2023-05-20

一、QSharedPointer的概述

QSharedPointer是Qt提供的一个用于管理动态分配的内存的类。他使用引用计数的方式管理内存,以便在没有被使用时自动释放内存,避免了内存泄漏问题。同时,QSharedPointer还提供了可扩展的保护性措施,使得我们能够保护对象免受不正确或意外的读写行为的影响。 下面是一个简单的QSharedPointer使用示例:

QSharedPointer<QString> ptr(new QString("Hello World"));

这里我们使用QSharedPointer来管理一个QString对象。在这个示例中,QString对象的引用计数为1,它将在QSharedPointer被销毁时被自动释放。

二、QSharedPointer的使用

1. 修改引用计数

QSharedPointer提供了两个成员函数来修改其所管理的对象的引用计数,它们分别是:增加引用计数的addRef()和减少引用计数的release()。下面是一个使用示例:

QSharedPointer<QString> ptr(new QString("Hello World"));
QSharedPointer<QString> ptr2 = ptr;
qDebug() << ptr.use_count();  // 输出2
ptr2.release();
qDebug() << ptr.use_count();  // 输出1

在这个示例中,我们创建了一个QString对象,并使用QSharedPointer将其进行管理。之后,我们将ptr赋值给ptr2,使得QString对象的引用计数增加到了2。然后,我们调用了ptr2release()函数将其指向的QString对象的引用计数减一。最后,我们通过ptr.use_count()函数输出了QString对象的引用计数,结果为1。

2. 扩展保护性措施

当QSharedPointer对象管理的指针被delete时,QSharedPointer可以通过调用一个我们指定的析构函数来执行任意自定义代码。这意味着我们可以以一种安全的方式扩展保护性措施,以避免意外的读写行为。下面是一个使用示例:

class Person {
public:
    Person() {
        qDebug() << "Person constructed!";
    }
    ~Person() {
        qDebug() << "Person destroyed!";
    }
};
void safeDelete(Person* ptr) {
    if (ptr != nullptr) {
        delete ptr;
        qDebug() << "Person deleted safely!";
    }
}
QSharedPointer<Person> ptr(new Person(), safeDelete);

在这个示例中,我们传递了一个自定义函数safeDelete作为第二个参数给QSharedPointer构造函数。这个函数将在QString对象被释放时被调用,以执行任意自定义代码。通过这种方式,我们可以扩展保护性措施,以避免意外的读写行为。

3. 对象类型转换

QSharedPointer提供了一些方便的方法来支持从一个类型转换为另一个类型,这些方法包括:staticCast()dynamicCast()constCast()qSharedPointerCast()。 下面是一个使用示例:

class Animal {
public:
    virtual ~Animal() {}
};
class Dog : public Animal {};
QSharedPointer<Dog> dog(new Dog());
QSharedPointer<Animal> animal = QSharedPointer<Animal>::dynamicCast(dog);

在这个示例中,我们创建了一个Dog对象,并使用QSharedPointer将其进行管理。之后,我们调用了dynamicCast()函数来将dog对象的类型转换为Animal类型。这使得我们可以在AnimalDog之间进行自由转换,而无需担心内存管理问题。

三、QSharedPointer的注意事项

1. 避免循环引用

由于QSharedPointer使用引用计数来管理内存,因此应该避免出现循环引用的情况。循环引用将导致对象永远无法被释放,从而造成内存泄漏。 下面是一个不当使用QSharedPointer引起循环引用的示例:

class Foo {
public:
    QSharedPointer<Foo> m_bar;
};
QSharedPointer<Foo> foo(new Foo);
foo->m_bar = foo;

在这个示例中,我们创建了一个Foo对象,并将其自身赋值给了m_bar成员变量。这样,Foo对象的引用计数永远不会降为0,导致内存泄漏。

2. 注意虚构顺序

QSharedPointer使用一个静态成员函数来删除被引用的对象,因此虚构的顺序非常重要。如果QSharedPointer析构函数中删除被管理对象后,还有其他成员变量需要访问被删除的对象,可能会导致意外行为。 下面是一个虚构顺序导致的问题示例:

class Foo {
public:
    ~Foo() {
        qDebug() << m_bar->getMessage();
    }
    QSharedPointer<Bar> m_bar;
};
class Bar {
public:
    QString getMessage() {
        return "Hello World!";
    }
};
QSharedPointer<Foo> foo(new Foo());
foo->m_bar.reset(new Bar());

在这个示例中,我们创建了一个Foo对象,并在其析构函数中尝试输出m_bar所指向的Bar对象中的消息。由于QSharedPointer析构函数是静态的,Bar对象在被删除后,Foo的析构函数将无法访问Bar对象的getMessage()函数,最终导致程序崩溃。

3. 慎用reset()函数

在使用QSharedPointer时,我们应该注意慎用reset()函数。reset()函数会释放QSharedPointer当前所管理的对象,并重新指定一个新的对象。如果我们在reset()函数中指定了原先被管理对象的指针,可能会导致内存泄漏。 下面是一个慎用reset()函数的示例:

QSharedPointer<QString> ptr(new QString("Hello World"));
ptr.reset(new QString("Hello Again"), [](QString* ptr) {
    qDebug() << "Deleting String...";
    delete ptr;
});

在这个示例中,我们首先使用QSharedPointer创建一个QString对象,并在其构造函数中指定一个删除器来在对象被释放时输出相关消息。之后,我们调用了reset()函数来替换被管理的对象。在reset()函数中,我们指定了一个新的QString对象,并指定原对象的删除器。这样一来,在第一次调用QString对象的删除器时,我们将输出两次删除相关的消息,造成不必要的困扰。

四、QSharedPointer的优缺点

1. 优点

使用QSharedPointer可以有效地避免内存泄漏问题,因为对象在没有被使用时将自动释放。 QSharedPointer还支持自定义析构函数来实现对对象的保护性措施,使得我们可以避免不正确或意外的读写行为。同时,QSharedPointer还提供了方便的类型转换和修改引用计数的函数。

2. 缺点

QSharedPointer的缺点是性能问题。因为它使用引用计数来管理内存,所以它必须在每次增加或减少引用计数时进行原子计数操作。虽然这些操作通常是很快的,但在某些情况下可能会成为程序的瓶颈。

五、总结

QSharedPointer是Qt提供的一个用于管理动态分配的内存的类,它使用引用计数的方式管理内存,以便在没有被使用时自动释放内存,避免了内存泄漏问题。同时,QSharedPointer还提供了可扩展的保护性措施和方便的类型转换和修改引用计数的函数。我们在使用QSharedPointer时,应该避免循环引用,注意虚构顺序和慎用reset()函数。虽然QSharedPointer有一些性能问题,但是由于其优秀的功能和安全性,它仍然是一个非常有价值的工具。