Qt 的 D-Pointer(Pimpl)設計模式
1. Pimpl 模式簡介
Pimpl(Pointer to Implementation)是一種設計模式,用于將類的接口與實現分離,從而隱藏實現細節,降低編譯依賴,提高代碼的可維護性和可擴展性。這種模式在 Qt 中被稱為 D-Pointer,廣泛應用于 Qt 框架中。
2. Pimpl 模式的優勢
- 隱藏實現細節:通過將私有成員變量和方法放在一個單獨的類中,頭文件中只包含一個指向該類的指針,從而隱藏了實現細節。
- 降低編譯依賴:修改私有實現不會影響到頭文件,因此不會引起重新編譯。
- 提高編譯速度:由于頭文件中只包含一個指針,編譯依賴減少,編譯速度提升。
- 保持穩定的 ABI:即使內部實現發生變化,只要接口保持不變,二進制接口(ABI)也保持穩定。
3. Qt 中的 D-Pointer 實現
在 Qt 中,D-Pointer 的實現主要通過以下宏來完成:
Q_DECLARE_PRIVATE
:在公共類中聲明一個指向私有實現類的指針。Q_DECLARE_PUBLIC
:在私有實現類中聲明一個指向公共類的指針。Q_D
和Q_Q
:在公共類和私有實現類中分別用于訪問對方。
4. 實現示例
公共類 ElaApplication
#include <QObject>
#include "ElaApplicationPrivate.h" // 包含私有實現類的聲明class ElaApplication : public QObject {Q_OBJECTQ_DECLARE_PRIVATE(ElaApplication) // 聲明私有實現類指針
public:explicit ElaApplication(QObject *parent = nullptr);~ElaApplication();void setIsEnableMica(bool enable);bool isEnableMica() const;void setMicaImagePath(const QString &path);QString micaImagePath() const;private:ElaApplicationPrivate *d_ptr; // 指向私有實現類的指針
};
私有實現類 ElaApplicationPrivate
#include "ElaApplication.h"class ElaApplicationPrivate {Q_DECLARE_PUBLIC(ElaApplication) // 聲明公共類指針
public:ElaApplicationPrivate(ElaApplication *q);~ElaApplicationPrivate();bool isEnableMica;QString micaImagePath;private:ElaApplication *q_ptr; // 指向公共類的指針
};
實現文件 ElaApplication.cpp
#include "ElaApplication.h"
#include "ElaApplicationPrivate.h"ElaApplication::ElaApplication(QObject *parent): QObject(parent), d_ptr(new ElaApplicationPrivate(this)) {Q_D(ElaApplication); // 使用 Q_D 宏獲取私有實現指針d->isEnableMica = false; // 初始化私有成員
}ElaApplication::~ElaApplication() {delete d_ptr; // 刪除私有實現
}void ElaApplication::setIsEnableMica(bool enable) {Q_D(ElaApplication); // 使用 Q_D 宏獲取私有實現指針d->isEnableMica = enable;
}bool ElaApplication::isEnableMica() const {Q_D(const ElaApplication); // 使用 Q_D 宏獲取私有實現指針return d->isEnableMica;
}void ElaApplication::setMicaImagePath(const QString &path) {Q_D(ElaApplication); // 使用 Q_D 宏獲取私有實現指針d->micaImagePath = path;
}QString ElaApplication::micaImagePath() const {Q_D(const ElaApplication); // 使用 Q_D 宏獲取私有實現指針return d->micaImagePath;
}
5. 總結
- Pimpl 模式:通過將實現細節隱藏在私有類中,減少編譯依賴,提高代碼的可維護性和可擴展性。
- Qt 的 D-Pointer:通過
Q_DECLARE_PRIVATE
、Q_DECLARE_PUBLIC
和Q_D
等宏,Qt 提供了一種簡潔的方式來實現 Pimpl 模式。 - 應用場景:Pimpl 模式在需要隱藏實現細節、減少編譯依賴和保持穩定的 ABI 的場景中非常有用。
希望這些信息能幫助你更好地理解 Qt 的 D-Pointer(Pimpl)設計模式!
示例代碼
公共類 Car
的頭文件
#include <QObject>class CarPrivate; // 前置聲明私有類class Car : public QObject {Q_OBJECT// Q_DECLARE_PRIVATE 宏展開// 原始宏定義:// #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;inline CarPrivate* d_func() { return reinterpret_cast<CarPrivate *>(qGetPtrHelper(d_ptr)); } // 獲取非const的私有實現inline const CarPrivate* d_func() const { return reinterpret_cast<const CarPrivate *>(qGetPtrHelper(d_ptr)); } // 獲取const的私有實現friend class CarPrivate; // 聲明私有實現類為友元類public:Car(QObject *parent = nullptr);~Car();void startEngine(); // 公共方法private:CarPrivate *d_ptr; // 指向私有實現類的指針
};
私有實現類 CarPrivate
的頭文件
#include "Car.h"class CarPrivate {// Q_DECLARE_PUBLIC 宏展開// 原始宏定義:// #define Q_DECLARE_PUBLIC(Class) \// Class *q_ptr; \// inline Class *q_func() { return q_ptr; } \// inline const Class *q_func() const { return q_ptr; }Car *q_ptr; // 指向公共類的指針inline Car *q_func() { return q_ptr; } // 獲取非const的公共類指針inline const Car *q_func() const { return q_ptr; } // 獲取const的公共類指針public:CarPrivate(Car *q) : q_ptr(q) {}bool engineStarted = false; // 私有成員變量
};
實現文件 Car.cpp
#include "Car.h"
#include "CarPrivate.h"// 構造函數
Car::Car(QObject *parent) : QObject(parent), d_ptr(new CarPrivate(this)) {// 初始化私有類
}// 析構函數
Car::~Car() {delete d_ptr; // 刪除私有類
}// startEngine 方法
void Car::startEngine() {// Q_D 宏展開// 原始宏定義:// #define Q_D(Class) Class##Private *const d = d_func()CarPrivate *const d = d_func(); // 獲取私有實現指針d->engineStarted = true; // 修改私有成員變量
}
詳細解釋
1. Q_DECLARE_PRIVATE
// 原始宏定義:
// #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;inline CarPrivate* d_func() { return reinterpret_cast<CarPrivate *>(qGetPtrHelper(d_ptr)); } // 獲取非const的私有實現
inline const CarPrivate* d_func() const { return reinterpret_cast<const CarPrivate *>(qGetPtrHelper(d_ptr)); } // 獲取const的私有實現
friend class CarPrivate; // 聲明私有實現類為友元類
d_func()
:提供了一個方法來訪問私有實現類的指針。friend class CarPrivate
:將私有實現類聲明為友元類,允許它訪問公共類的私有成員。
2. Q_DECLARE_PUBLIC
// 原始宏定義:
// #define Q_DECLARE_PUBLIC(Class) \
// Class *q_ptr; \
// inline Class *q_func() { return q_ptr; } \
// inline const Class *q_func() const { return q_ptr; }Car *q_ptr; // 指向公共類的指針
inline Car *q_func() { return q_ptr; } // 獲取非const的公共類指針
inline const Car *q_func() const { return q_ptr; } // 獲取const的公共類指針
q_ptr
:在私有實現類中聲明一個指向公共類的指針。q_func()
:提供了一個方法來訪問公共類的指針。
3. Q_D
// 原始宏定義:
// #define Q_D(Class) Class##Private *const d = d_func()CarPrivate *const d = d_func(); // 獲取私有實現指針
Q_D
:簡化代碼,通過d_func()
獲取私有實現類的指針,并將其存儲在局部變量d
中。
完整的示例代碼
公共類 Car
的頭文件
#include <QObject>class CarPrivate; // 前置聲明私有類class Car : public QObject {Q_OBJECT// Q_DECLARE_PRIVATE 宏展開inline CarPrivate* d_func() { return reinterpret_cast<CarPrivate *>(qGetPtrHelper(d_ptr)); } // 獲取非const的私有實現inline const CarPrivate* d_func() const { return reinterpret_cast<const CarPrivate *>(qGetPtrHelper(d_ptr)); } // 獲取const的私有實現friend class CarPrivate; // 聲明私有實現類為友元類public:Car(QObject *parent = nullptr);~Car();void startEngine(); // 公共方法private:CarPrivate *d_ptr; // 指向私有實現類的指針
};
私有實現類 CarPrivate
的頭文件
#include "Car.h"class CarPrivate {// Q_DECLARE_PUBLIC 宏展開Car *q_ptr; // 指向公共類的指針inline Car *q_func() { return q_ptr; } // 獲取非const的公共類指針inline const Car *q_func() const { return q_ptr; } // 獲取const的公共類指針public:CarPrivate(Car *q) : q_ptr(q) {}bool engineStarted = false; // 私有成員變量
};
實現文件 Car.cpp
#include "Car.h"
#include "CarPrivate.h"// 構造函數
Car::Car(QObject *parent) : QObject(parent), d_ptr(new CarPrivate(this)) {// 初始化私有類
}// 析構函數
Car::~Car() {delete d_ptr; // 刪除私有類
}// startEngine 方法
void Car::startEngine() {// Q_D 宏展開CarPrivate *const d = d_func(); // 獲取私有實現指針d->engineStarted = true; // 修改私有成員變量
}
總結
d_ptr
:通過Q_DECLARE_PRIVATE
宏聲明,是一個指向私有實現類的指針。d_func()
:通過Q_DECLARE_PRIVATE
宏定義,用于獲取私有實現類的指針。Q_D
:簡化代碼,通過d_func()
獲取私有實現類的指針,并存儲在局部變量d
中。
1. Q_DECLARE_PRIVATE
宏的定義
Q_DECLARE_PRIVATE
宏的定義如下:
#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;
2. 宏的展開
假設我們有一個類 Car
,其私有實現類為 CarPrivate
。使用 Q_DECLARE_PRIVATE(Car)
宏后,代碼會展開為:
inline CarPrivate* d_func() { return reinterpret_cast<CarPrivate *>(qGetPtrHelper(d_ptr)); } // 獲取非const的私有實現
inline const CarPrivate* d_func() const { return reinterpret_cast<const CarPrivate *>(qGetPtrHelper(d_ptr)); } // 獲取const的私有實現
friend class CarPrivate; // 聲明私有實現類為友元類
3. d_ptr
的作用
在 Q_DECLARE_PRIVATE
宏中,d_ptr
是一個特定的變量名,它被用來存儲指向私有實現類的指針。Q_DECLARE_PRIVATE
宏生成的代碼依賴于 d_ptr
,因為它假設類中有一個名為 d_ptr
的成員變量。
4. 如果將 d_ptr
改為 fadsfdas_ptr
如果你將 d_ptr
改為 fadsfdas_ptr
,Q_DECLARE_PRIVATE
宏生成的代碼將無法正確工作,因為宏生成的代碼仍然會嘗試訪問 d_ptr
,而不是 fadsfdas_ptr
。
修改后的代碼
class Car : public QObject {Q_OBJECT// Q_DECLARE_PRIVATE 宏展開inline CarPrivate* d_func() { return reinterpret_cast<CarPrivate *>(qGetPtrHelper(d_ptr)); } // 獲取非const的私有實現inline const CarPrivate* d_func() const { return reinterpret_cast<const CarPrivate *>(qGetPtrHelper(d_ptr)); } // 獲取const的私有實現friend class CarPrivate; // 聲明私有實現類為友元類public:Car(QObject *parent = nullptr);~Car();void startEngine(); // 公共方法private:CarPrivate *fadsfdas_ptr; // 指向私有實現類的指針
};
問題
d_func()
方法仍然嘗試訪問d_ptr
,但d_ptr
不存在。Q_DECLARE_PRIVATE
宏生成的代碼依賴于d_ptr
,而不是fadsfdas_ptr
。
5. 自定義宏
如果你需要使用不同的變量名(例如 fadsfdas_ptr
),你需要手動實現類似 Q_DECLARE_PRIVATE
的功能。這可以通過自定義宏來完成,但不推薦,因為這會增加代碼的復雜性和維護難度。
自定義宏
#define Q_DECLARE_PRIVATE_EX(Class, PtrName) \inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(PtrName)); } \inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(PtrName)); } \friend class Class##Private;class Car : public QObject {Q_OBJECTQ_DECLARE_PRIVATE_EX(Car, fadsfdas_ptr) // 使用自定義宏public:Car(QObject *parent = nullptr);~Car();void startEngine(); // 公共方法private:CarPrivate *fadsfdas_ptr; // 指向私有實現類的指針
};
6. 詳細說明
(1)Q_DECLARE_PRIVATE
宏的依賴
Q_DECLARE_PRIVATE
宏依賴于 d_ptr
,因為它假設類中有一個名為 d_ptr
的成員變量。這個變量名是固定的,宏生成的代碼會直接使用它。
(2)d_func()
方法
d_func()
方法通過 qGetPtrHelper(d_ptr)
提取 d_ptr
的原始指針,并將其轉換為 CarPrivate *
類型。如果 d_ptr
不存在,d_func()
方法將無法正確工作。
(3)friend class CarPrivate
friend class CarPrivate
聲明私有實現類為友元類,允許它訪問公共類的私有成員。這個聲明與 d_ptr
的名字無關,但它是 Q_DECLARE_PRIVATE
宏的一部分。
7. 總結
Q_DECLARE_PRIVATE
宏依賴于d_ptr
:它生成的代碼假設類中有一個名為d_ptr
的成員變量。- 改變變量名會導致問題:如果將
d_ptr
改為其他名稱(例如fadsfdas_ptr
),宏生成的代碼將無法正確工作。 - 推薦使用默認的
d_ptr
:為了保持代碼的一致性和可維護性,建議使用默認的d_ptr
。