Qt 智能指针学习

从内存泄露开始?

很简单的入门程序,应该比较熟悉吧 ^_^

#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QLabel *label = new QLabel("Hello Dbzhang800!");
    label->show();
    return app.exec();
}

  从 Qt 的 delete 说开来 一文中,我们提到这个程序存在内存泄露(表现就是析构函数不被调用),而且当时给出了三种解决方法:

  • 将label对象分配到stack而不是heap中
  • 给label设置标记位Qt::WA_DeleteOnClose

  • 自己调用delete来删除通过new分配到heap中的 label 对象

注:

  • 为了能清楚观察构造和析构函数的调用,我们可以简单子类化了一下QLabel,然后用Label取代前面的QLabel
    class Label :public QLabel
    {
    public:
        Label(const QString& text, QWidget *parent=NULL)
            :QLabel(text, parent){qDebug("from constructor");}
        ~Label(){qDebug("from destructor");}
    };

本文中,我们从智能指针(smart pointer)角度继续考虑这个问题

智能指针

为了管理内存等资源,C++程序员通常采用RAII(Resource Acquisition Is Initialization)机制:在类的构造函数中申请资源,然后使用,最后在析构函数中释放资源。

如果没有智能指针,程序员必须保证new对象能在正确的时机delete,四处编写异常捕获代码以释放资源,而智能指针则可以在退出作用域时(不管是正常流程离开或是因异常离开)总调用delete来析构在堆上动态分配的对象。

我们看看Qt家族的智能指针:

智能指针

 

引入

QPointer

Qt Object 模型的特性(之一)
注意:析构时不会delete它管理的资源

 

QSharedPointer

带引用计数

Qt4.5

QWeakPointer

 

Qt4.5

QScopedPointer

 

Qt4.6

QScopedArrayPointer

QScopedPointer的派生类

Qt4.6

QSharedDataPointer

用来实现Qt的隐式共享(Implicit Sharing)

Qt4.0

QExplicitlySharedDataPointer

显式共享

Qt4.4

   

std::auto_ptr

 
 

std::shared_ptr

std::tr1::shared_ptr

C++0x

std::weak_ptr

std::tr1::weak_ptr

C++0x

std::unique_ptr

boost::scoped_ptr

C++0x

注:

  • MSVC2010 和 GCC g++ 4.3 支持 C++0x
  • MSVC2008 sp1 及 GCC g++ 4.0 支持 tr1

有了这些东西,我们就可以很容易改造我们前面的例子了(只需改变一行):

std::auto_ptr<QLabel> label(new QLabel("Hello Dbzhang800!"));

根据你所用的Qt的版本,以及C++编译器的支持程度,你可以选用:

  • QScopedPointer
  • std::unique_ptr
  • QSharedPointer
  • std::shared_ptr
    • std::tr1::shared_ptr

QPointer

如何翻译呢?我不太清楚,保留英文吧。

  • The QPointer class is a template class that provides  guarded pointers   to QObjects.

  • 使用:一个guarded指针,QPointer<T> ,行为和常规的指针 T * 类似

  • 特点:当其指向的对象(T必须是QObject及其派生类)被销毁时,它会被自动置NULL.
    • 注意:它本身析构时不会自动销毁所guarded的对象
  • 用途:当你需要保存其他人所拥有的QObject对象的指针时,这点非常有用

一个例子

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

如果在...部分你将该对象delete掉了,label会自动置NULL,而不会是一个悬挂(dangling)的野指针。

QPointer 属于Qt Object模型的核心机制之一,请注意和其他智能指针的区别。

std::auto_ptr

这个没多少要说的。

  • 不能让多个auto_ptr 指向同一个对象!(auto_ptr被销毁时会自动删除它指向的对象,这样对象会被删除多次)
    • 通过拷贝构造或赋值进行操作时,被拷贝的会自动变成NULL,复制所得的指针将获得资源的唯一所有权
  • 智能指针不能指向数组(因为其实现中调用的是delete而非delete[])
  • 智能指针不能作为容器类的元素。

在C++0x中,auto_ptr已经不建议使用,以后应该会被其他3个智能指针所取代。

QScopedPointer 与 std::unique_ptr

它们概念上应该是是一样的。下面不再区分:

这是一个很类似auto_ptr的智能指针,它包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除。但它的所有权更加严格,不能转让,一旦获取了对象的管理权,你就无法再从它那里取回来。

无论是QScopedPointer 还是 std::unique_ptr 都拥有一个很好的名字,它向代码的阅读者传递了明确的信息:这个智能指针只能在本作用域里使用,不希望被转让。因为它的拷贝构造和赋值操作都是私有的,这点我们可以对比QObject及其派生类的对象哈。

用法 (来自Qt的manual):

考虑没有智能指针的情况,

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

我们在异常处理语句中多次书写delete语句,稍有不慎就会导致资源泄露。采用智能指针后,我们就可以将这些异常处理语句简化了:

 void myFunction(bool useSubClass)
 {
     QScopedPointer<MyClass> p(useSubClass ? new MyClass() : new MySubClass);
     QScopedPointer<QIODevice> device(handsOverOwnership());

     if (m_value > 3)
         return;

     process(device);
 }

另,我们一开始的例子,也是使用这两个指针的最佳场合了(出main函数作用域就将其指向的对象销毁)。

注意:因为拷贝构造和赋值操作私有的,它也具有auto_ptr同样的“缺陷”——不能用作容器的元素。

QSharedPointer 与 std::shared_ptr

QSharedPointer 与 std::shared_ptr 行为最接近原始指针,是最像指针的"智能指针",应用范围比前面的提到的更广。

QSharedPointer 与 QScopedPointer 一样包装了new操作符在堆上分配的动态对象,但它实现的是引用计数型的智能指针 ,可以被自由地拷贝和赋值,在任意的地方共享它,当没有代码使用(引用计数为0)它时才删除被包装的动态分配的对象。shared_ptr也可以安全地放到标准容器中,并弥补了std::auto_ptr 和 QScopedPointer 因为转移语义而不能把指针作为容器元素的缺陷。

QWeakPointer 与 std::weak_ptr

强引用类型的QSharedPointer已经非常好用,为什么还要有弱引用的 QWeakPointer?

QWeakPointer 是为配合 QSharedPointer 而引入的一种智能指针,它更像是 QSharedPointer 的一个助手(因为它不具有普通指针的行为,没有重载operator*和->)。它的最大作用在于协助 QSharedPointer 工作,像一个旁观者一样来观测资源的使用情况。

  • weak_ptr 主要是为了避免强引用形成环状。摘自msdn中一段话:
    • A cycle occurs when two or more resources controlled by shared_ptr objects hold mutually referencing shared_ptr objects. For example, a circular linked list with three elements has a head node N0; that node holds a shared_ptr object that owns the next node, N1; that node holds a shared_ptr object that owns the next node, N2; that node, in turn, holds a shared_ptr object that owns the head node, N0, closing the cycle. In this situation, none of the reference counts will ever become zero, and the nodes in the cycle will not be freed. To eliminate the cycle, the last node N2 should hold a weak_ptr object pointing to N0 instead of a shared_ptr object. Since the weak_ptr object does not own N0 it doesn't affect N0's reference count, and when the program's last reference to the head node is destroyed the nodes in the list will also be destroyed.
  • 在Qt中,对于QObject及其派生类对象,QWeakPointer有特殊处理。它可以作为QPointer的替代品
    • 这种情况下,不需要QSharedPointer的存在
    • 效率比 QPointer 高

QSharedDataPointer

这是为配合 QSharedData 实现隐式共享(写时复制 copy-on-write))而提供的便利工具。

Qt中众多的类都使用了隐式共享技术,比如QPixmap、QByteArray、QString、...。而我们为自己的类实现隐式共享也很简单,比如要实现一个 Employee类:

  • 定义一个只含有一个数据成员(QSharedDataPointer<EmployeeData>) 的 Employee 类

  • 我们需要的所有数据成员放置于 派生自QSharedData的 EmployeeData类中。

具体实现看 QSharedDataPointer 的Manual,此处略

QExplicitlySharedDataPointer

这是为配合 QSharedData 实现显式共享而提供的便利工具。

QExplicitlySharedDataPointer 和 QSharedDataPointer 非常类似,但是它禁用了写时复制功能。这使得我们创建的对象更像一个指针。

一个例子,接前面的Employee:

 #include "employee.h"

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

写时复制技术导致:e1和e2有相同的工号,但有不同名字。与我们期待的不同,显式共享可以解决这个问题,这也使得Employee本身更像一个指针。

补遗

先前竟未注意到官方的这两篇文章(这是失败):
便看看google编码规范中对3个智能指针的建议:
scoped_ptr
Straightforward and risk-free. Use wherever appropriate.
auto_ptr
Confusing and bug-prone ownership-transfer semantics. Do not use.
shared_ptr
Safe with const referents (i.e. shared_ptr<const T> ). Reference-counted pointers with non-const referents can occasionally be the best design, but try to rewrite with single owners where possible.

参考

<think>好的,我现在需要回答用户关于Qt中P指针和D指针的用法和原理的问题。首先,我得回忆一下Qt的相关知识,特别是D指针(d_ptr)和P指针(q_ptr)的作用和实现方式。 根据用户提供的引用内容,引用[2]和[3]提到了WidgetPrivate结构体中的q_ptr,这是一个指向公开接口类的指针。而D指针通常是指d_ptr,用于指向私有实现类,这种设计属于Pimpl(Pointer to Implementation)模式,有助于隐藏实现细节,减少头文件依赖,提高编译速度。 引用[3]中的Q_DECLARE_PUBLIC宏定义了q_func()函数,将q_ptr转换为公开类指针,并声明友元类,这样私有类可以访问公开类的成员。引用[4]则提到D指针的好处,比如隐藏实现细节、提高编译速度等。而引用[1]虽然主要讲QPointer,但也提到了智能指针Qt中的重要性,可能和资源管理有关,但用户的问题更关注D和P指针。 接下来,我需要组织答案的结构,先解释D指针和P指针的基本概念,然后说明它们的实现原理,包括如何通过宏来关联两者。还要提到使用场景和优势,比如二进制兼容性、封装性等。可能还需要对比Pimpl模式,说明Qt是如何具体应用这一模式的。 需要注意的是,用户的引用中有提到D指针的其他好处,比如隐藏实现细节和加快编译速度,这些也应该涵盖进去。同时,要确保使用正确的中文术语,并按照要求使用LaTeX格式的行内公式,例如在说明指针转换时如果有数学表达式的话。不过这个问题可能更多涉及代码和设计模式,数学公式可能较少,但如果有相关的地方需要注意格式。 另外,用户可能混淆了P指针和QPointer,但根据引用内容,P指针在这里应该是指q_ptr,即指向公开类的指针,而QPointer是另一种智能指针,用于防止悬挂指针,这点需要区分清楚。在回答中需要明确指出两者的不同,避免用户误解。 最后,生成相关问题时要围绕D/P指针的应用、优势、实现细节等方面提问,比如设计模式、使用好处、具体代码实现等。确保问题符合用户的学习需求,帮助他们深入理解该机制。</think>### Qt中P指针与D指针的实现原理 Qt框架中的**D指针(d_ptr)**和**P指针(q_ptr)**是**Pimpl(Pointer to Implementation)**设计模式的具体实现,主要用于接口与实现的分离。以下是具体原理和用法: --- #### **1. D指针(d_ptr)的作用** - **定义**:D指针指向一个**私有实现类**(通常命名为`ClassNamePrivate`),该类包含所有数据成员和实现细节[^4]。 - **实现方式**: ```cpp // widget.h class Widget { public: Widget(); private: WidgetPrivate *d_ptr; // D指针 }; ``` - **优点**: - **二进制兼容性**:修改私有类不会影响头文件,避免重新编译依赖库[^4]。 - **封装性**:隐藏实现细节,仅公开必要的接口[^4]。 --- #### **2. P指针(q_ptr)的作用** - **定义**:P指针存在于私有实现类中,指向**公开接口类**(即`Widget`类本身),用于从私有类反向访问公有接口[^2][^3]。 - **实现方式**: ```cpp // widget_p.h struct WidgetPrivate { WidgetPrivate(Widget *q) : q_ptr(q) {} // 构造函数初始化q_ptr Widget *q_ptr; // P指针 Rect geometry; String stylesheet; }; ``` --- #### **3. D指针与P指针的关联** - **宏定义**: - `Q_DECLARE_PRIVATE`:在公有类中声明访问私有类的辅助函数。 - `Q_DECLARE_PUBLIC`:在私有类中声明反向访问公有类的函数[^3]。 ```cpp // 在Widget类中使用 #define Q_DECLARE_PRIVATE(Class) \ inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(d_ptr); } // 在WidgetPrivate中使用 #define Q_DECLARE_PUBLIC(Class) \ inline Class* q_func() { return static_cast<Class *>(q_ptr); } \ friend class Class; ``` - **访问流程**: - 公有类通过`d_func()`获取私有类实例。 - 私有类通过`q_func()`获取公有类实例。 --- #### **4. 实际应用示例** ```cpp // widget.cpp Widget::Widget() : d_ptr(new WidgetPrivate(this)) { Q_D(Widget); // 展开为 WidgetPrivate *d = d_func(); d->geometry = Rect(0, 0, 100, 100); } // 私有类访问公有类 void WidgetPrivate::updateStyle() { Q_Q(Widget); // 展开为 Widget *q = q_func(); q->setStyleSheet(stylesheet); } ``` --- #### **5. 优势与注意事项** - **优势**: - **编译加速**:头文件不暴露实现细节,减少依赖编译[^4]。 - **二进制兼容**:修改私有类不影响动态库的ABI兼容性[^4]。 - **注意事项**: - 需通过宏和友元类管理指针权限。 - 私有类需在`_p.h`文件中定义,并注明“非公开API”。 ---
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值