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.xdnf.cn/news/6048.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

HTML第一次作业

制作带有下拉悬停菜单的导航栏 <!DOCTYPE html> <html> <head><meta charset"utf-8"><title>带有下拉悬停菜单的导航栏</title><style>* {margin: 0;padding: 0;}#menu {background-color: blue;width: 100%;height: 50p…

数据结构 C/C++(实验三:队列)

&#xff08;大家好&#xff0c;今天分享的是数据结构的相关知识&#xff0c;大家可以在评论区进行互动答疑哦~加油&#xff01;&#x1f495;&#xff09; 目录 提要&#xff1a;实验题目 一、实验目的 二、实验内容及要求 三、算法思想 实验1 实验2 四、源程序及注释…

Chromium127编译指南 Mac篇(二)- 安装Xcode

1. 概述 在Chromium开发的道路上&#xff0c;为Mac平台搭建正确的环境是至关重要的第一步。本文将聚焦于Xcode的安装过程&#xff0c;它作为Mac上不可或缺的开发工具&#xff0c;为Chromium的编译提供了必要的编译器和工具链。 我们将详细解析如何选择、下载并安装适合Chromium…

移动应用开发 实验二:标准身高计算器

文章目录 准备工作一&#xff0c;创建Android Studio项目二&#xff0c;创建活动模块三&#xff0c;设计用户界面&#xff08;一&#xff09;设置页面布局&#xff08;二&#xff09;添加标题文本控件&#xff08;三&#xff09;设计体重输入框&#xff08;四&#xff09;设计性…

这个超级棒,我收藏的样机素材,统统分享给你们

Hello&#xff0c;大家好&#xff0c;我是后期圈&#xff01;工作不息摸鱼不止&#xff01;继续给圈友们带来有趣好玩的推荐&#xff0c;圈友们在摸鱼的同时别忘记多多点赞支持一波呀 今天给大家分享 4个超实用效率高质量网站&#xff0c;请各位偷偷收藏使用&#xff0c;懂得都…

管理 Elasticsearch 变得更容易了,非常容易!

作者&#xff1a;来自 Elastic Ken Exner Elasticsearch 用户&#xff0c;我们听到了你的心声。管理 Elasticsearch 有时会变得很复杂&#xff0c;面临的挑战包括性能调整、问题检测和资源优化。我们一直致力于简化你的体验。今天&#xff0c;我们宣布了自收购 Opster 以来的一…

spark-本地模式的配置和简单使用

python环境的安装 在虚拟机中&#xff0c;只能安装一个python的版本&#xff0c;若想要安装别的版本&#xff0c;则需要卸载之前的版本——解决方式&#xff0c;安装Anaconda 通过百度网盘分享的文件&#xff1a;Anaconda3-2021.05-Linux-x86_64.sh 链接&#xff1a;https://…

将vscode的终端改为cygwin terminal

现在终端是默认的power shell&#xff0c;没有显示cygwin 接下来选择默认配置文件 找到cygwin的选项即可 然后提示可能不安全什么的&#xff0c;点是&#xff0c;就有了

Node.js简介以及安装部署 (基础介绍 一)

Node.js简介 Node.js是运行在服务端的JavaScript。 Node.js是一个基于Chrome JavaScript运行时建立的一个平台。 Node.js是一个事件驱动I/O服务端JavaScript环境&#xff0c;基于Google的V8引擎&#xff0c;V8引擎执行Javascript的速度非常快&#xff0c;性能非常好。 Node.…

【MySQL 保姆级教学】深层理解索引及其特性(重点)--上(11)

MySQL与磁盘 1. MySQL与内存和磁盘的联系2. 认识磁盘2.1 MySQL与存储2.2 磁盘结构2.3 扇区2.4 定位扇区 3. MySQL与磁盘交互基本单位4. 建立共识5. 索引的理解5.1 建立一个表并查询5.2 为何 I/O 交互要是Page 6. B树 Vs B 树数6.1 不同存储引擎支持的索引结构类型6.2 B树 Vs B树…

修改云服务器远程默认端口

操作场景 由于使用系统默认端口的风险较大&#xff0c;容易被攻击软件扫描以及攻击&#xff0c;为避免因端口攻击而无法远程连接云服务器&#xff0c;您可将云服务器默认远程端口修改为不常见的端口&#xff0c;提高云服务器的安全性。 修改服务端口需在安全组规则与云服务器…

0xGame 2024 [Week 4] Jenkins

1.前言 由于好久没做web题了&#xff0c;所以今天来尝试来做一波web题&#xff0c;仅供刷题记录。 2.题目 这个给的提示对于小白来说实在是友好的过劲。 3.分析 上网搜到一个关于Jenkins的历史漏洞&#xff0c;下面链接可供参考 https://blog.csdn.net/2301_80127209/arti…

10天进阶webpack---(1)为什么要有webpack

首先就是我们的代码是运行在浏览器上的&#xff0c;但是我们开发大多都是利用node进行开发的&#xff0c;在浏览器中并没有node提供的那些环境。这就造成了运行和开发上的不同步问题。 -----引言 浏览器模块化的问题&#xff1a; 效率问题&#xff1a;精细的模块划分带来了更…

好累-还要复习

第一次碰到无极值改变区间长度特征值的关系迹对应的特征向量是原来的一列 共轭的考虑两项相加 那么就有两种情况 观察数列函数&#xff0c;构建拉格朗日&#xff08;非常重要&#xff09;

Maven从浅入深(理解篇)

前言 在软件开发领域&#xff0c;包管理器是不可或缺的工具&#xff0c;它们帮助开发者管理和维护项目中的依赖库。通过对比.NET的NuGet包、Python的pip包以及Java的Maven&#xff0c;我们可以从原理上更深刻地理解这些工具的作用和差异。 1. NuGet&#xff08;.NET&#xff0…

Ollama AI 框架缺陷可能导致 DoS、模型盗窃和中毒

近日&#xff0c;东方联盟网络安全研究人员披露了 Ollama 人工智能 (AI) 框架中的六个安全漏洞&#xff0c;恶意行为者可能会利用这些漏洞执行各种操作&#xff0c;包括拒绝服务、模型中毒和模型盗窃。 知名网络安全专家、东方联盟创始人郭盛华表示&#xff1a;“总的来说&…

【多模态读论文系列】MINIGPT-4论文笔记

【多模态读论文系列】LLaMA-Adapter V2论文笔记 【多模态读论文系列】LLaVA论文笔记 分享第三篇多模态论文阅读笔记 MINIGPT-4: ENHANCING VISION-LANGUAGE UNDERSTANDING WITH ADVANCED LARGE LANGUAGE MODELS 论文地址&#xff1a;https://arxiv.org/pdf/2304.10592 代码…

安信金控:古法金与普通金的区别

古法金和普通金在制作工艺、外观特点、硬度和耐磨性以及价格等方面存在明显差异。本文详细比较了古法金与普通金的区别&#xff0c;供大家参考。 一、制作工艺 1. 古法金 古法金采用传统的铸金工艺&#xff0c;过程复杂且耗时。主要工艺包括&#xff1a; 搂胎&#xff1a;使…

2023下半年上午(22~38)

二十二、 选A 现在定义一个函数&#xff0c;里面有非静态的局部变量f1 在栈区stack里面&#xff0c;先是主函数main入栈&#xff0c;然后调用main里面的方法&#xff0c;即function&#xff08;&#xff09;入栈&#xff0c;在入栈的一瞬间&#xff0c;局部变量f1就被定义了&a…

使用 GPT-4V 全面评估泛化情绪识别 (GER)

概述 由于情绪在人机交互中扮演着重要角色&#xff0c;因此情绪识别备受研究人员关注。目前的情感识别研究主要集中在两个方面&#xff1a;一是识别刺激物引起的情感&#xff0c;并预测观众观看这些刺激物后的感受。另一个方面是分析图像和视频中的人类情绪。在本文中&#xf…