Pimpl(Pointer to Implementation)模式详解

Pimpl(Pointer to Implementation)模式详解

在 C++ 中,Pimpl 模式(Pointer to Implementation)是一种设计技巧,常用于隐藏实现细节,减少头文件的依赖。这种模式又被称为“隐式实现”或“编译防护模式”,因为它通过隐藏类的私有成员,能有效减少编译依赖、提高封装性、优化编译时间。本文将介绍 Pimpl 模式的原理、优缺点及其在实际项目中的使用场景。

为什么需要 Pimpl 模式?

在 C++ 项目中,头文件和实现文件的分离是一种常见的结构,但这也导致了依赖性问题。尤其是当类中使用复杂的第三方库或系统资源时,头文件可能会暴露很多实现细节,这会带来以下问题:

  1. 暴露依赖:如果头文件中包含了第三方库的具体类型或变量,这些依赖就必须公开给所有引用它的文件,从而增大了依赖范围。

  2. 增加编译时间:每次修改头文件,所有包含该头文件的源文件都需要重新编译。对复杂项目而言,这会显著延长编译时间。

  3. 破坏封装性:头文件中暴露的私有成员,可能会被其他模块或开发人员依赖,使得代码的可维护性下降。

为了解决这些问题,Pimpl 模式通过在头文件中隐藏类的私有实现,让实现细节仅在 .cpp 文件中可见,从而有效隔离头文件依赖。

Pimpl 模式的实现方式

基本原理

Pimpl 模式的核心是将类的实现细节封装在一个独立的类中,然后通过一个指针(通常是智能指针)引用该实现类,从而达到隐藏实现的目的。其步骤如下:

  1. 创建实现类:将类的私有成员和方法放入一个名为 Impl 的实现类中。

  2. 声明指向实现类的指针:在头文件的类中,声明一个指向 Impl 的指针。

  3. .cpp 文件中定义实现类:在 .cpp 文件中实现所有细节,从而避免在头文件中包含复杂的依赖。

示例代码

以下是一个使用 Pimpl 模式的完整示例,展示了如何将实现细节封装在一个独立的 Impl 类中。

不使用 Pimpl 模式的代码
头文件:SQLiteDatabase.h
#ifndef SQLITE_DATABASE_H
#define SQLITE_DATABASE_H#include "Database.h"
#include <sqlite3.h>               // 不使用 Pimpl 时,必须在头文件中包含 sqlite3.h
#include <string>
#include <vector>class SQLiteDatabase : public Database {
public:SQLiteDatabase();virtual ~SQLiteDatabase() override;bool connect(const std::string& connectionString) override;void disconnect() override;bool executeQuery(const std::string& query) override;std::vector<std::vector<std::string>> fetchResults(const std::string& query) override;private:sqlite3* m_db;                 // 直接在头文件中使用 SQLite 的指针bool m_isConnected;
};#endif // SQLITE_DATABASE_H
实现文件:SQLiteDatabase.cpp
#include "SQLiteDatabase.h"
#include <iostream>SQLiteDatabase::SQLiteDatabase() : m_db(nullptr), m_isConnected(false) {}SQLiteDatabase::~SQLiteDatabase() {disconnect();
}bool SQLiteDatabase::connect(const std::string& connectionString) {if (m_isConnected) {return true;}int rc = sqlite3_open(connectionString.c_str(), &m_db);if (rc != SQLITE_OK) {std::cerr << "Can't open database: " << sqlite3_errmsg(m_db) << std::endl;return false;}m_isConnected = true;return true;
}void SQLiteDatabase::disconnect() {if (m_isConnected) {sqlite3_close(m_db);m_db = nullptr;m_isConnected = false;}
}bool SQLiteDatabase::executeQuery(const std::string& query) {if (!m_isConnected) {std::cerr << "Database is not connected." << std::endl;return false;}char* errMsg = nullptr;int rc = sqlite3_exec(m_db, query.c_str(), nullptr, nullptr, &errMsg);if (rc != SQLITE_OK) {std::cerr << "SQL error: " << errMsg << std::endl;sqlite3_free(errMsg);return false;}return true;
}std::vector<std::vector<std::string>> SQLiteDatabase::fetchResults(const std::string& query) {std::vector<std::vector<std::string>> results;if (!m_isConnected) {std::cerr << "Database is not connected." << std::endl;return results;}sqlite3_stmt* stmt;int rc = sqlite3_prepare_v2(m_db, query.c_str(), -1, &stmt, nullptr);if (rc != SQLITE_OK) {std::cerr << "Failed to prepare statement: " << sqlite3_errmsg(m_db) << std::endl;return results;}while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {std::vector<std::string> row;for (int i = 0; i < sqlite3_column_count(stmt); ++i) {const char* text = reinterpret_cast<const char*>(sqlite3_column_text(stmt, i));row.push_back(text ? text : "NULL");}results.push_back(row);}sqlite3_finalize(stmt);return results;
}
使用 Pimpl 模式的代码
头文件:SQLiteDatabase.h
#ifndef SQLITE_DATABASE_H
#define SQLITE_DATABASE_H#include "Database.h"
#include <string>
#include <vector>namespace BL {class DLL_API SQLiteDatabase : public Database {public:SQLiteDatabase();virtual ~SQLiteDatabase() override;bool connect(const std::string& connectionString, bool createIfNotExists = false) override;void disconnect() override;bool executeQuery(const std::string& query) override;std::vector<std::vector<std::string>> fetchResults(const std::string& query) override;private:class Impl;           // 声明内部实现类Impl* m_impl;         // 指向内部实现类的指针};
}#endif // SQLITE_DATABASE_H
实现文件:SQLiteDatabase.cpp
#include "SQLiteDatabase.h"
#include <sqlite3.h>
#include <iostream>
#include <fstream>namespace BL {// 定义内部实现类class SQLiteDatabase::Impl {public:Impl() : m_db(nullptr) , m_isConnected(false) {}~Impl() {if ( m_isConnected ) {sqlite3_close(m_db);}}bool connect(const std::string& connectionString , bool createIfNotExists) {if ( m_isConnected ) {return true; // 已连接}if ( !createIfNotExists && !std::ifstream(connectionString) ) {std::cerr << "Database file does not exist: " << connectionString << std::endl;return false;}int rc = sqlite3_open(connectionString.c_str() , &m_db);if ( rc != SQLITE_OK ) {std::cerr << "Can't open database: " << sqlite3_errmsg(m_db) << std::endl;return false;}m_isConnected = true;return true;}void disconnect() {if ( m_isConnected ) {sqlite3_close(m_db);m_db = nullptr;m_isConnected = false;}}bool executeQuery(const std::string& query) {if ( !m_isConnected ) {std::cerr << "Database is not connected." << std::endl;return false;}char* errMsg = nullptr;int rc = sqlite3_exec(m_db , query.c_str() , nullptr , nullptr , &errMsg);if ( rc != SQLITE_OK ) {std::cerr << "SQL error: " << errMsg << std::endl;sqlite3_free(errMsg);return false;}return true;}std::vector<std::vector<std::string>> fetchResults(const std::string& query) {std::vector<std::vector<std::string>> results;if ( !m_isConnected ) {std::cerr << "Database is not connected." << std::endl;return results; // 返回空结果}sqlite3_stmt* stmt;int rc = sqlite3_prepare_v2(m_db , query.c_str() , -1 , &stmt , nullptr);if ( rc != SQLITE_OK ) {std::cerr << "Failed to prepare statement: " << sqlite3_errmsg(m_db) << std::endl;return results;}while ( ( rc = sqlite3_step(stmt) ) == SQLITE_ROW ) {std::vector<std::string> row;for ( int i = 0; i < sqlite3_column_count(stmt); ++i ) {const char* text = reinterpret_cast< const char* >( sqlite3_column_text(stmt , i) );row.push_back(text ? text : "NULL");}results.push_back(row);}if ( rc != SQLITE_DONE ) {std::cerr << "Failed to execute statement: " << sqlite3_errmsg(m_db) << std::endl;}sqlite3_finalize(stmt);return results;}private:sqlite3* m_db;bool m_isConnected;};// 构造和析构函数中分配和释放 Impl 对象SQLiteDatabase::SQLiteDatabase() : m_impl(new Impl()) {}SQLiteDatabase::~SQLiteDatabase() {delete m_impl;}bool SQLiteDatabase::connect(const std::string& connectionString , bool createIfNotExists) {return m_impl->connect(connectionString , createIfNotExists);}void SQLiteDatabase::disconnect() {m_impl->disconnect();}bool SQLiteDatabase::executeQuery(const std::string& query) {return m_impl->executeQuery(query);}std::vector<std::vector<std::string>> SQLiteDatabase::fetchResults(const std::string& query) {return m_impl->fetchResults(query);}
} // namespace BL

Pimpl 模式的优缺点

优点
  1. 隐藏实现细节:类的实现完全封装在 .cpp 文件中,头文件暴露的只有接口部分。

  2. 减少依赖:头文件不再依赖具体的库或第三方组件,使得依赖更加简化。

  3. 加快编译速度:修改实现类不再影响头文件,减少了重新编译的模块数量。

  4. 提高封装性:增强类的封装性,其他模块无法依赖类的私有成员。

缺点
  1. 额外的动态分配:使用 Pimpl 需要为实现类动态分配内存,带来轻微的性能开销。

  2. 额外的间接访问:对实现的访问需要通过指针,增加了函数调用的间接性。

  3. 复杂性增加:需要维护额外的实现类,代码结构比直接实现稍微复杂。

总结

Pimpl 模式是一种简单而有效的 C++ 设计模式,尤其适合大型项目和需要隐藏实现细节的模块。通过将实现类封装在 .cpp 文件中,它可以显著减少依赖、提升封装性和优化编译速度。在选择是否使用 Pimpl 模式时,可以根据项目的复杂度和对封装的需求做出权衡。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/59378.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

js下载excel示例demo

<Buttontype{"primary"}key"out"onClick{async ()>{const ExportJsonExcel require("js-export-excel");const datas selectedRowsState //确保勾到的数据是一个列表&#xff0c;列表中每个值是字典const option {};const dataTable […

mac 修改启动图图标数量

调整每行显示图标数量&#xff1a; defaults write com.apple.dock springboard-rows -int 7 调整每列显示的数量 defaults write com.apple.dock springboard-columns -int 8 最后重置一下启动台 defaults write com.apple.dock ResetLaunchPad -bool TRUE;killall Dock 其…

Go使用SIMD指令——以string转为整数为例

本文Go使用SIMD指令采用如下方式&#xff1a; C编写对应的程序clang编译成汇编c2goasm将上述生成的汇编转为go的汇编 准备工具 clang。直接使用apt-get install clang安装即可c2goasm。 go get -u github.com/minio/c2goasm来进行安装asm2plan9s。 go get -u github.com/min…

【算法】递归+深搜+哈希表:889.根据前序和后序遍历构造二叉树

目录 1、题目链接 相似题目: 2、题目 ​3、解法&#xff08;针对无重复值&#xff0c;哈希表递归&#xff09; 函数头-----找出重复子问题 函数体---解决子问题 4、代码 1、题目链接 889.根据前序和后序遍历构造二叉树&#xff08;LeetCode&#xff09; 相似题目: 105.…

【矩阵的大小和方向的分解】

“大小”&#xff1a;在特征值分解和奇异值分解中&#xff0c;矩阵的“大小”通常由特征值或者奇异值表示&#xff0c;它们描述了矩阵在不同方向上拉伸或压缩的程度。“方向”&#xff1a;特征向量和奇异值分解中的方向矩阵 ( U ) 和 ( V ) 则描述了矩阵作用下空间中各个方向的…

【AIGC】如何充分利用ChatGPT:有效提示框架与基本规则

概述 在使用ChatGPT进行内容创作时&#xff0c;遵循结构化的提示框架和基本规则可以显著提升AI响应的质量。本文探讨了五种结构化的提示框架&#xff0c;并详细介绍了基本规则和进阶技巧&#xff0c;帮助您更有效地与ChatGPT互动。 基础规则 规则1&#xff1a;指令放在开头&…

高级信号完整性

高级信号完整性&#xff0c;2022年版&#xff0c;1473页&#xff0c;24h秒发 内容庞大&#xff0c;都是新的内容、架构 QS排名100内的美国高校课件 发货内容&#xff1a; 29个分章节PDF 1个汇总PDF&#xff0c;1473页 点击获取 课程首先对电磁学进行了回顾。随后&#xff0c;…

yelp数据集上识别潜在的热门商家

yelp数据集是研究B2C业态的一个很好的数据集&#xff0c;要识别潜在的热门商家是一个多维度的分析过程&#xff0c;涉及用户行为、商家特征和社区结构等多个因素。从yelp数据集里我们可以挖掘到下面信息有助于识别热门商家 用户评分和评论分析 评分均值: 商家的平均评分是反映其…

qt QDataStream详解

1. 概述 QDataStream是Qt框架中的一个核心类&#xff0c;主要用于处理二进制数据的序列化和反序列化。它提供了高效、跨平台的方式&#xff0c;将C数据结构转化为字节流&#xff0c;便于在网络传输、持久化存储等场景下使用。QDataStream可以处理包括整数、浮点数、布尔值、字…

使用Spring Validation实现数据校验详解

目录 前言1. Spring Validation概述2. 配置Spring Validation2.1 引入依赖2.2 启用全局校验 3. 使用注解进行参数校验3.1 基本校验注解3.2 使用Pattern进行正则校验3.3 综合示例 4. 在控制器层应用校验4.1 方法参数校验4.2 自定义错误处理 5. 高级应用&#xff1a;自定义校验注…

链表(C 语言)

目录 一、链表的概念1. 链表的结构2. 链表的分类3. 链表的优势 二、链表的实现1. 无头单项非循环链表的实现1.1 代码说明 2. 带头双向循环链表的实现2.1 代码说明 三、链表和顺序表的区别四、链表总结 一、链表的概念 链表是一种顺序表&#xff0c;它由一个一个的节点组成&…

写文件回前端进行下载,报错:原因:CORS 头缺少 ‘Access-Control-Allow-Origin‘)

后端写文件返回前端&#xff0c;出现该错误。 解决 设置允许跨域 response.setHeader("Access-Control-Allow-Origin", "*"); 代码 后端 public void exportTemplate(HttpServletResponse response) { ArrayList<ActiveGifts> activeGifts new…

关注AI技术的应用前景,抓住未来科技发展的机遇!

在当今这个快速发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;技术正以惊人的速度改变着我们的生活和工作方式。无论是在医疗、金融、教育还是制造业&#xff0c;AI的应用都在不断扩展&#xff0c;带来前所未有的机遇和挑战。关注AI技术的应用前景&#xff0c;不仅…

QinQ的基础实验

拓扑 命令 LSW1 [LSW1]vlan batch 2 3 4 Info: This operation may take a few seconds. Please wait for a moment...done. [LSW1]interface g0/0/1 [LSW1-GigabitEthernet0/0/1]port link-type hybrid [LSW1-GigabitEthernet0/0/1]port hybrid untagged vlan 2 3 [LSW…

python-读写Excel:openpyxl-(4)下拉选项设置

使用openpyxl库的DataValidation对象方法可添加下拉选择列表。 DataValidation参数说明&#xff1a; type&#xff1a; 数据类型("whole", "decimal", "list", "date", "time", "textLength", "custom"…

Elasticsearch中时间字段格式用法详解

Elasticsearch中时间字段格式用法详解 攻城狮Jozz关注IP属地: 北京 2024.03.18 16:27:51字数 758阅读 2,571 Elasticsearch&#xff08;简称ES&#xff09;是一个基于Lucene构建的开源、分布式、RESTful搜索引擎。它提供了全文搜索、结构化搜索以及分析等功能&#xff0c;广泛…

【JavaEE初阶 — 多线程】Thread的常见构造方法&属性

目录 Thread类的属性 1.Thread 的常见构造方法 2.Thread 的几个常见属性 2.1 前台线程与后台线程 2.2 setDaemon() 2.3 isAlive() Thread类的属性 Thread 类是JVM 用来管理线程的一个类&#xff0c;换句话说&#xff0c;每个线程都有一个唯一的Thread 对象与之关联&am…

论文阅读笔记:DRCT: Saving Image Super-Resolution away from Information Bottleneck

论文阅读笔记&#xff1a;DRCT: Saving Image Super-Resolution away from Information Bottleneck 1 背景1.1 问题1.2 本文提出的方法 2 创新点3 方法4 模块4.1 问题描述4.2 深度特征提取模块4.3 同任务渐进式训练策略 5 效果5.1 和SOTA方法对比 论文&#xff1a;https://arxi…

基于STM32的智能空气净化器设计

引言 本项目基于STM32微控制器设计了一个智能空气净化器&#xff0c;通过集成多个传感器模块和Wi-Fi模块&#xff0c;实现空气质量监测、净化以及远程控制功能。该系统可以实时检测环境中的空气质量&#xff0c;如PM2.5浓度、温湿度和有害气体浓度等&#xff0c;根据监测到的空…

Linux 无名管道

无名管道&#xff08;unnamed pipe&#xff09;是一种进程间通信的方式&#xff0c;通常用于父子进程之间的通信。下面是使用无名管道的基本步骤&#xff1a; pipe 调用的返回值如下&#xff1a; 成功时&#xff1a;pipe 调用成功时返回 0。失败时&#xff1a;如果 pipe 调用…