D-Pointer(Pimpl)設計模式(指向實現的指針)

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_DQ_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_PRIVATEQ_DECLARE_PUBLICQ_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_ptrQ_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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/81008.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/81008.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/81008.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MySQL 8.0 OCP 1Z0-908 101-110題

Q101.which two queries are examples of successful SQL injection attacks? A.SELECT id, name FROM backup_before WHERE name‘; DROP TABLE injection; --’; B. SELECT id, name FROM user WHERE id23 oR id32 OR 11; C. SELECT id, name FROM user WHERE user.id (SEL…

Vue ElementUI原生upload修改字體大小和區域寬度

Vue ElementUI原生upload修改字體大小和區域寬度 修改后 代碼 新增的修改樣式代碼 .upload-demo /deep/ .el-upload-dragger{width: 700px;height: 300px; }原有拖拽組件代碼 <!-- 拖拽上傳組件 --><el-uploadclass"upload-demo"dragaction"":m…

React和Vue在前端開發中, 通常選擇哪一個

React和Vue的選擇需結合具體需求&#xff1a; 選React的場景 大型企業級應用&#xff0c;需處理復雜狀態&#xff08;如電商、社交平臺&#xff09;團隊熟悉JavaScript&#xff0c;已有React技術棧積累需要高度靈活的架構&#xff08;React僅專注視圖層&#xff0c;可自由搭配…

Python爬蟲實戰:研究源碼還原技術,實現逆向解密

1. 引言 在網絡爬蟲技術實際應用中,目標網站常采用各種加密手段保護數據傳輸和業務邏輯。傳統逆向解密方法依賴人工分析和調試,效率低下且易出錯。隨著 Web 應用復雜度提升,特別是 JavaScript 混淆技術廣泛應用,傳統方法面臨更大挑戰。 本文提出基于源碼還原的逆向解密方法…

什么是alpaca 或 sharegpt 格式的數據集?

環境&#xff1a; LLaMA-Factory 問題描述&#xff1a; alpaca 或 sharegpt 格式的數據集&#xff1f; 解決方案&#xff1a; “Alpaca”和“ShareGPT”格式的數據集&#xff0c;是近年來在開源大語言模型微調和對話數據構建領域比較流行的兩種格式。它們主要用于訓練和微調…

OpenCV CUDA模塊中矩陣操作------矩陣元素求和

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 在OpenCV的CUDA模塊中&#xff0c;矩陣元素求和類函數主要用于計算矩陣元素的總和、絕對值之和以及平方和。這些操作對于圖像處理中的特征提取、…

給視頻加一個動畫。

為什么要給視頻加一個動畫&#xff1f; 很完整的視頻也就是從短動畫開始的。遮蓋住LOG用。 C:\Users\Sam\Desktop\desktop\startup\workpython\ocr Lottie.py import subprocessdef run_ffmpeg(cmd):print("Running:", " ".join(cmd))subprocess.run(cm…

15:00開始面試,15:06就出來了,問的問題有點變態。。。

從小廠出來&#xff0c;沒想到在另一家公司又寄了。 到這家公司開始上班&#xff0c;加班是每天必不可少的&#xff0c;看在錢給的比較多的份上&#xff0c;就不太計較了。沒想到4月一紙通知&#xff0c;所有人不準加班&#xff0c;加班費不僅沒有了&#xff0c;薪資還要降40%…

使用命令行拉取 Git 倉庫

1. 克隆遠程倉庫&#xff08;首次獲取&#xff09; # 克隆倉庫到當前目錄&#xff08;默認使用 HTTPS 協議&#xff09; git clone https://github.com/用戶名/倉庫名.git# 克隆倉庫到指定目錄 git clone https://github.com/用戶名/倉庫名.git 自定義目錄名# 使用 SSH 協議克隆…

如何禁止chrome自動更新

百度了一下 下面這個方法實測有效 目錄 1、WINR 輸入 services.msc 2、在Services彈窗中找到下面兩個service并disable 3、驗證是否禁止更新成功&#xff1a; 1、WINR 輸入 services.msc 2、在Services彈窗中找到下面兩個service并disable GoogleUpdater InternalService…

數據庫事務以及JDBC實現事務

一、數據庫事務 數據庫事務&#xff08;Database Transaction&#xff09;是數據庫管理系統中的一個核心概念&#xff0c;它代表一組操作的集合&#xff0c;這些操作要么全部執行成功&#xff0c;要么全部不執行&#xff0c;即操作數據的最小執行單元&#xff0c;保證數據庫的…

【vue】【環境配置】項目無法npm run serve,顯示node版本過低

解決方案&#xff1a;安裝高版本node&#xff0c;并且啟用高版本node 步驟&#xff1a; 1、查看當前版本 node -v2、配置nvm下載鏡像源 1&#xff09;查看配置文件位置 npm root2&#xff09;找到settings.txt文件 修改鏡像源為&#xff1a; node_mirror: https://npmmirro…

WPF之INotifyPropertyChanged實現

文章目錄 引言INotifyPropertyChanged接口基礎接口定義工作原理 基本實現方式標準實現示例CallerMemberName特性 高級實現技術基類實現通知多個屬性變化使用PropertyChanging事件 MVVM框架中的實現MVVM模式簡介MVVM框架中的實現Prism框架MVVM Light框架自定義MVVM基類 性能優化…

【MCP教程系列】SpringBoot 搭建基于 Spring AI 的 SSE 模式 MCP 服務

原文地址&#xff1a;https://developer.aliyun.com/article/1662946 在當今快速發展的AI技術背景下&#xff0c;如何高效地集成模型能力成為開發者關注的重點。本文將手把手教你如何基于 Spring AI 搭建支持 SSE&#xff08;Server-Sent Events&#xff09;模式的 MCP 服務 相…

springboot集成langchain4j實現票務助手實戰

前言 看此篇的前置知識為langchain4j整合springboot&#xff0c;以及springboot集成langchain4j記憶對話。 Function-Calls介紹 langchain4j 中的 Function Calls&#xff08;函數調用&#xff09;是一種讓大語言模型&#xff08;LLM&#xff09;與外部工具&#xff08;如 A…

MySQL-數據庫分布式XA事務

準備 innodb存儲引擎開啟支持分布式事務 set global innodb_support_axonMySQL數據庫XA事務的SQL語法如下&#xff1a; XA {START| BEGIN} xid {JOIN | RESUME} XA END xid {SUSPEND [ FOR MIGRATE]} XA PREPARE xid XA COMMIT xid [ONE PHASE] XA ROLLBACK xid XA RECOVER 完…

SAP 運維-冷門問題解決辦法

1.SAP Fiori幫助菜單鏈接如何配置&#xff1f; 答&#xff1a; 執行事務代碼HELP_CONFIG&#xff0c;選擇對應的Fiori部署模式&#xff0c;配置幫助菜單下的URL鏈接。 檢查配置的幫助菜單&#xff0c;執行事務代碼/N//UI2/FLP_CUS_CONF 或者SR13進行查看配置狀態與修改。

新型智慧園區技術架構深度解析:數字孿生與零碳科技的融合實踐

&#x1f3ed;在杭州亞運村零碳園區&#xff0c;光伏板與氫燃料大巴構成的能源網絡&#xff0c;正通過數字孿生技術實現智能調度。這不僅是格力電器與龍源電力在新能源領域的創新實踐&#xff0c;更是智慧園區4.0時代的標桿案例。當AI算法開始接管能源調度&#xff0c;當BIM建模…

Java轉Go日記(三十六):簡單的分布式

1.1.1. 簡單的分布式server 目前分布式系統已經很流行了&#xff0c;一些開源框架也被廣泛應用&#xff0c;如dubbo、Motan等。對于一個分布式服務&#xff0c;最基本的一項功能就是服務的注冊和發現&#xff0c;而利用zk的EPHEMERAL節點則可以很方便的實現該功能。EPHEMERAL節…

機器學習筆記——特征工程

大家好&#xff0c;這里是好評筆記&#xff0c;公主號&#xff1a;Goodnote&#xff0c;專欄文章私信限時Free。本筆記介紹機器學習中常見的特征工程方法、正則化方法和簡要介紹強化學習。 文章目錄 特征工程&#xff08;Fzeature Engineering&#xff09;1. 特征提取&#xff…