如何使用Qt的PIMPL习惯用法(PIMPL Idiom

2024-06-24 10:12

本文主要是介绍如何使用Qt的PIMPL习惯用法(PIMPL Idiom,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

PIMPL是指“Pointer to IMPLementation”(指向实现的指针),意味着将实现细节隐藏起来,用户类无需关注这些实现细节。在Qt中常用PIMPL习惯用法来清晰地区分接口与实现,尽管Qt官方文档并未详细说明该机制。本文将演示如何在Qt中使用PIMPL习惯用法,并以一个简单的坐标输入对话框作为实例。

原理与动机

PIMPL的核心在于将类的实现细节封装在一个私有类中,这个私有类包含了所有特定于实现的数据和方法。通过这种方式,可以将头文件中的实现细节移除,减少头文件的依赖。

// CoordinateDialog.h
#include <QDialog>
#include <QVector3D>class CoordinateDialogPrivate;  // 前向声明私有类
class CoordinateDialog : public QDialog
{Q_OBJECTQ_DECLARE_PRIVATE(CoordinateDialog)  // 声明私有指针
#if QT_VERSION <= QT_VERSION_CHECK(5,0,0)Q_PRIVATE_SLOT(d_func(), void onAccepted())  // 兼容Qt 4的私有槽
#endifQScopedPointer<CoordinateDialogPrivate> const d_ptr;  // 智能指针管理私有类实例
public:CoordinateDialog(QWidget * parent = 0, Qt::WindowFlags flags = 0);~CoordinateDialog();QVector3D coordinates() const;Q_SIGNAL void acceptedCoordinates(const QVector3D &);
};
Q_DECLARE_METATYPE(QVector3D)

对比非PIMPL接口

不使用PIMPL的接口如下所示,尽管实现更为简单,但是头文件中包含了大量实现细节,违反了接口与实现分离的原则。

// CoordinateDialog.h
#include <QDialog>
#include <QVector3D>
#include <QFormLayout>
#include <QDoubleSpinBox>
#include <QDialogButtonBox>class CoordinateDialog : public QDialog
{QFormLayout m_layout;QDoubleSpinBox m_x, m_y, m_z;QVector3D m_coordinates;QDialogButtonBox m_buttons;Q_SLOT void onAccepted();
public:CoordinateDialog(QWidget * parent = 0, Qt::WindowFlags flags = 0);QVector3D coordinates() const;Q_SIGNAL void acceptedCoordinates(const QVector3D &);
};
Q_DECLARE_METATYPE(QVector3D)

实现细节

下面详细介绍PIMPL实现的各个部分。

接口文件(Header)部分

// CoordinateDialog.h
#include <QDialog>
#include <QVector3D>class CoordinateDialogPrivate;  // 前向声明
class CoordinateDialog : public QDialog
{Q_OBJECTQ_DECLARE_PRIVATE(CoordinateDialog)  // 声明私有指针
#if QT_VERSION <= QT_VERSION_CHECK(5,0,0)Q_PRIVATE_SLOT(d_func(), void onAccepted())  // 兼容Qt 4的私有槽
#endifQScopedPointer<CoordinateDialogPrivate> const d_ptr;  // 智能指针管理私有类实例
public:CoordinateDialog(QWidget * parent = 0, Qt::WindowFlags flags = 0);~CoordinateDialog();QVector3D coordinates() const;Q_SIGNAL void acceptedCoordinates(const QVector3D &);
};
Q_DECLARE_METATYPE(QVector3D)

实现文件(Source)部分

// CoordinateDialog.cpp
#include "CoordinateDialog.h"
#include <QFormLayout>
#include <QDoubleSpinBox>
#include <QDialogButtonBox>// 私有类的定义
class CoordinateDialogPrivate {Q_DISABLE_COPY(CoordinateDialogPrivate)  // 禁用拷贝Q_DECLARE_PUBLIC(CoordinateDialog)  // 声明公有指针CoordinateDialog * const q_ptr;  // 指向公有类实例的指针QFormLayout layout;QDoubleSpinBox x, y, z;QDialogButtonBox buttons;QVector3D coordinates;void onAccepted();CoordinateDialogPrivate(CoordinateDialog*);
};CoordinateDialogPrivate::CoordinateDialogPrivate(CoordinateDialog *dialog) :q_ptr(dialog),layout(dialog),buttons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
{layout.addRow("X", &x);layout.addRow("Y", &y);layout.addRow("Z", &z);layout.addRow(&buttons);dialog->connect(&buttons, SIGNAL(accepted()), SLOT(accept()));dialog->connect(&buttons, SIGNAL(rejected()), SLOT(reject()));
#if QT_VERSION <= QT_VERSION_CHECK(5,0,0)this->connect(dialog, SIGNAL(accepted()), SLOT(onAccepted()));
#elseQObject::connect(dialog, &QDialog::accepted, [this]{ onAccepted(); });
#endif
}void CoordinateDialogPrivate::onAccepted() {Q_Q(CoordinateDialog);  // 声明指向公有类实例的指针coordinates.setX(x.value());coordinates.setY(y.value());coordinates.setZ(z.value());emit q->acceptedCoordinates(coordinates);
}CoordinateDialog::CoordinateDialog(QWidget *parent, Qt::WindowFlags flags) :QDialog(parent, flags),d_ptr(new CoordinateDialogPrivate(this))  // 初始化私有数据指针
{}QVector3D CoordinateDialog::coordinates() const {Q_D(const CoordinateDialog);  // 声明指向私有数据的指针return d->coordinates;
}CoordinateDialog::~CoordinateDialog() {}

关键宏解释

  • Q_DECLARE_PRIVATE(Class): 声明私有指针,d_func()返回指向私有类实例的指针。
  • Q_PRIVATE_SLOT(instance_pointer, method signature): 用来声明私有槽,仅用于Qt 4或非C++11兼容项目。
  • Q_DISABLE_COPY(Class): 禁用复制和赋值操作。
  • Q_DECLARE_PUBLIC(Class): 声明公有指针,q_func()返回指向公有类实例的指针。

常见问题与注意事项

  1. 头文件包含顺序:在实现文件中,接口文件必须首先包含。
  2. 私有类的定义:私有类不能在公有类内部定义。
  3. Q_ 宏的使用:宏的定义中已经包含了分号,因此无需额外添加分号。

这篇关于如何使用Qt的PIMPL习惯用法(PIMPL Idiom的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1089852

相关文章

POI从入门到实战轻松完成EasyExcel使用及Excel导入导出功能

《POI从入门到实战轻松完成EasyExcel使用及Excel导入导出功能》ApachePOI是一个流行的Java库,用于处理MicrosoftOffice格式文件,提供丰富API来创建、读取和修改O... 目录前言:Apache POIEasyPoiEasyExcel一、EasyExcel1.1、核心特性

Java 如何创建和使用ExecutorService

《Java如何创建和使用ExecutorService》ExecutorService是Java中用来管理和执行多线程任务的一种高级工具,可以有效地管理线程的生命周期和任务的执行过程,特别是在需要处... 目录一、什么是ExecutorService?二、ExecutorService的核心功能三、如何创建

使用FileChannel实现文件的复制和移动方式

《使用FileChannel实现文件的复制和移动方式》:本文主要介绍使用FileChannel实现文件的复制和移动方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录使用 FileChannel 实现文件复制代码解释使用 FileChannel 实现文件移动代码解释

Java中的getBytes()方法使用详解

《Java中的getBytes()方法使用详解》:本文主要介绍Java中getBytes()方法使用的相关资料,getBytes()方法有多个重载形式,可以根据需要指定字符集来进行转换,文中通过代... 目录前言一、常见重载形式二、示例代码三、getBytes(Charset charset)和getByt

Java使用Stream流的Lambda语法进行List转Map的操作方式

《Java使用Stream流的Lambda语法进行List转Map的操作方式》:本文主要介绍Java使用Stream流的Lambda语法进行List转Map的操作方式,具有很好的参考价值,希望对大... 目录背景Stream流的Lambda语法应用实例1、定义要操作的UserDto2、ListChina编程转成M

Spring框架中@Lazy延迟加载原理和使用详解

《Spring框架中@Lazy延迟加载原理和使用详解》:本文主要介绍Spring框架中@Lazy延迟加载原理和使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、@Lazy延迟加载原理1.延迟加载原理1.1 @Lazy三种配置方法1.2 @Component

使用easy connect之后,maven无法使用,原来需要配置-Djava.net.preferIPv4Stack=true问题

《使用easyconnect之后,maven无法使用,原来需要配置-Djava.net.preferIPv4Stack=true问题》:本文主要介绍使用easyconnect之后,maven无法... 目录使用easGWowCy connect之后,maven无法使用,原来需要配置-DJava.net.pr

使用Java编写一个字符脱敏工具类

《使用Java编写一个字符脱敏工具类》这篇文章主要为大家详细介绍了如何使用Java编写一个字符脱敏工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、字符脱敏工具类2、测试工具类3、测试结果1、字符脱敏工具类import lombok.extern.slf4j.Slf4j

pandas DataFrame keys的使用小结

《pandasDataFramekeys的使用小结》pandas.DataFrame.keys()方法返回DataFrame的列名,类似于字典的键,本文主要介绍了pandasDataFrameke... 目录Pandas2.2 DataFrameIndexing, iterationpandas.DataF

使用Python和PaddleOCR实现图文识别的代码和步骤

《使用Python和PaddleOCR实现图文识别的代码和步骤》在当今数字化时代,图文识别技术的应用越来越广泛,如文档数字化、信息提取等,PaddleOCR是百度开源的一款强大的OCR工具包,它集成了... 目录一、引言二、环境准备2.1 安装 python2.2 安装 PaddlePaddle2.3 安装