PImpl机制以及Qt的D-Pointer实现

本文深入解析了C++中的PImpl(PointertoImplementation)技术及其在Qt库中的实现——D-Pointer。PImpl机制通过将类的实现细节封装在独立的实现类中,达到数据隐藏、缩短编译时间和保持ABI稳定等优点。文章详细介绍了Qt中如何使用D-Pointer,包括宏定义的使用和优化版本的实现,展示了如何通过继承QObjectPrivate来减少每个子类的实现负担。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

PImpl是 Pointer to implementation的缩写, 是C++ 在构建导出库接口时特有的技术手段。 即是将类Class中所有私有变量以及私有方法,封装在一单独的实现类ClassImpl中。我们在Class中通过一指向ClassImpl的私有指针,访问这些私有数据。而ClassImpl类的具体定义和实现,我们放入cpp中。Qt中的D-Pointer技术,便是PImpl机制的一种实现方式。
优点:

使得程序接口有着稳定的ABI(应用程序二进制接口),即不会打破二进制兼容。
降低了程序编译依赖,从而缩短编译时间。
数据隐藏,使得头文件很干净,不包含实现细节,可以直接作为 API 参考。

缺点:

实现者需要做更多工作。
由于子类需要访问,此机制对protected方法不奏效。
由于数据的隐藏,多少造成了代码可读性下降。
运行时性能被轻微的连累,尤其调用的函数为虚函数时。

关于二进制兼容的问题,在C++的二进制兼容一文中有详细解释,这里不再赘述。下面我们通过介绍Qt的D-Pointer的实现细节,来理解PImpl机制。

Qt中D-Pointer的实现
Person.h

class PersonPrivate;
class Person
{
    Q_DECLARE_PRIVATE(Person)
public:
    Person();
    ~Person();
    QString name() const;
    void setName(const QString &name);
signals:
    void calcRequested();
private:
    QScopedPointer<PersonPrivate> d_ptr;
};

Person_p.h

#include "Person.h"
class PersonPrivate
{
    Q_DECLARE_PUBLIC(Person)
public:
    PersonPrivate(Person *parent);
    
    void calc();
    
    QString name;
private:
    Person * const q_ptr;
};

Person.cpp

#include "Person_p.h"
Person::Person() : d_ptr(new PersonPrivate(this))
{
}
~Person() {}
QString Person::name() const
{
    Q_D(const Person);
    return d->name;
}
void Person::setName(const QString &name)
{
    Q_D(const Person);
    d->name = name;
}
PersonPrivate::PersonPrivate(Person *parent) : q_ptr(parent)
{
  
}
void PersonPrivate::calc()
{
    Q_Q(Person);
    emit q->calcRequested();
}

相关宏定义及作用

以上所有用到的宏定义,均放在qglobal.h中。下面我们一一介绍。
Q_DECLARE_PRIVATE、Q_D

template <typename T> static inline T *qGetPtrHelper(T *ptr) { return ptr; }
template <typename Wrapper> static inline typename Wrapper::pointer qGetPtrHelper(const Wrapper &p) { return p.data(); }
#define Q_DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
    inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \
    friend class Class##Private;
    
#define Q_D(Class) Class##Private * const d = d_func()

Q_DECLARE_PRIVATE看似复杂,其实就是封装了d_func()函数,目的就是让我们在多种不同情况下,可以方便地拿到并使用私有类指针d_ptr 。Q_D宏对d_func()进行再次封装,让我们可以免去每次定义的繁琐,直接使用d指针,此指针即为我们想要的d_ptr。下面我们进行更详细的理解以及相关注意事项:

利用d_func()函数,可以避免我们每次直接拿d_ptr指针进行类型转换(因为我们有可能会在子类中使用此方法,具体我们将在后面的拓展中详述)。
在d_func()中,我们为什么不直接使用d_ptr ,而要借助qGetPtrHelper()函数呢?利用此函数,是为了适配我们使用智能指针的情况,因为此时我们要拿到真正的指针,需要调用d_ptr.data()。
在const函数中使用Q_D,此时将调用d_func()的const版本,我们必须要利用Q_D(const Person)这种写法拿到正确的const指针(否则会提示无法转换)。这样封装也间接保证了程序的正确性,而不是直接拿到d_ptr指针进行操作 。
d_ptr的定义是要放在暴露给用户的头文件中,如此命名有时会打破我们的命名规范,此时可以利用Q_DECLARE_PRIVATE_D(m_dPtr, Person)这个宏来进行自定义的命名。看到这个宏,我们不得不感慨Qt封装的细致得当。
既然上面提到了使用智能指针,这里多说几句,我们利用前置声明的方式来使用QScopedPointer时,我们必须要有非内联的构造、析构、赋值运算符。即不可以用默认生成的。具体可参见QScopedPointer文档中的Forward Declared Pointers部分。

Q_DECLARE_PUBLIC、Q_Q

#define Q_DECLARE_PUBLIC(Class)                                    \
    inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
    inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
    friend class Class;
    
#define Q_Q(Class) Class * const q = q_func()

同理,我们在私有类中,有时候需要调用主类的方法,这两个宏的作用就是为了可以在私有类中拿到主类的指针。我们在私有类的构造函数中传入主类指针,并赋值给q_ptr。因为这里是拿到主类的指针,并不存在智能指针的问题,所以此处并没有借助qGetPtrHelper()函数。
拓展

有了上面的讲解,我们到这里可以思考一个问题,假如我们的类有很多的子类,那么我们岂不是每一个子类都需要定义一个d_ptr。每一个private类都需要有一个q_ptr的指针么?Qt中当然不会如此实现,所以就有了下面的优化版本。

首先我们在基类QObject中将d_ptr变为protected类型,并在基类中添加一protected类型的构造函数,供子类使用。

class QObject
{
protected:
    QObject(QObjectPrivate &dd, QObject *parent = 0); 
    QScopedPointer<QObjectData> d_ptr;
    ...
};

所有的私有类均继承于QObjectPrivate,

class QWidgetPrivate : public QObjectPrivate
{
    Q_OBJECT
    Q_DECLARE_PUBLIC(QWidget)
    ...
};

下面我们在看看QWidget和QObject的构造函数:

QWidget::QWidget(QWidget *parent, Qt::WindowFlags f)     
        : QObject(*new QWidgetPrivate, 0), QPaintDevice()  
{ 
    ... 
}
QObject::QObject(QObject *parent)
    : d_ptr(new QObjectPrivate)
{
    Q_D(QObject);
    d_ptr->q_ptr = this;
};
QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
{
    Q_D(QObject);
    d_ptr->q_ptr = this;
};

到这里,总算真相大白,QWidget中并没有出现d_ptr指针,原来是从Qbject继承而来。QObject中我们新添加的那个protected构造函数传入一个QWidgetPrivate,用此给QObject中的d_ptr赋值,而这便是我们唯一的d_ptr。现在总算真正理解之前d_func()中那些类型转换的作用,就是保证我们可以拿到当前正确类型的private指针。

那么同理,ObjectPrivate是继承于QObjectData,而在QObjectData中有着QObject *q_ptr;。 所有QObject子类的私有类,均继承于ObjectPrivate,故而子类中也不会出现q_ptr,在QObject的构造函数中,我们把this指针给其赋值,在通过使用Q_Q宏,我们同样可以拿到正确类型的主类q指针。
总结

我们完全可以不借助Qt这些宏来实现PImpl,其实只需要构建private类,将其放入cpp中,就已经实现了PImpl。不过利用这些宏,可以简单的实现出Qt风格的数据隐藏,我们可以利用上面Person类的简化版实现,当然假如我们的类需要被继承,我们也可以参考拓展中的方式,利用继承ObjectPrivate类的方式实现,不过需要注意,想要使用此类,我们需要在pro中添加QT += core-private。

参考:D-Point
https://wiki.qt.io/D-Pointer/zh

<think>好的,我现在需要回答用户关于Qt中P指针和D指针的用法和原理的问题。首先,我得回忆一下Qt的相关知识,特别是D指针(d_ptr)和P指针(q_ptr)的作用和实现方式。 根据用户提供的引用内容,引用[2]和[3]提到了WidgetPrivate结构体中的q_ptr,这是一个指向公开接口类的指针。而D指针通常是指d_ptr,用于指向私有实现类,这种设计属于PimplPointer 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)**是**PimplPointer 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”。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值