首页 > 编程语言 >C++ PImpl模式、指向实现的指针、PImpl Idiom、隐藏实现细节

C++ PImpl模式、指向实现的指针、PImpl Idiom、隐藏实现细节

时间:2024-07-14 14:26:01浏览次数:18  
标签:实现 C++ PImpl Idiom MyClass pImpl Impl 指针

C++ PImpl模式、指向实现的指针、PImpl Idiom、隐藏实现细节

flyfish

PImpl 全称是 “Pointer to Implementation”,在中文中通常翻译为“指向实现的指针”或者“指向实现”。PImpl 是一种编程技巧,通常用于 C++ 中,通过这种技术,可以隐藏类的实现细节,达到信息隐藏和二进制兼容性的目的。PImpl 也被称为“编译防火墙”(Compilation Firewall)。

PImpl 模式在遇到以下情况下可以使用:

如果是在简单场景下,不使用 PImpl 的实现更直接,如果遇到下面的情况,就可以使用PImpl 的方式。

1 隐藏实现细节:

如果你想要隐藏类的内部实现细节,使得用户只需要知道类的接口,而无需了解具体实现。
这在发布库或 API 时特别有用,可以避免用户代码依赖于类的具体实现,从而提高封装性。

2 减少编译依赖:

如果你的类实现可能经常改变,而不希望每次改变都导致所有依赖该类的代码重新编译。
例如,当一个类的成员变量或内部实现发生变化时,使用 PImpl 模式可以避免重新编译所有包括这个头文件的文件。

3提高二进制兼容性:

在需要保持 ABI(应用程序二进制接口)稳定的场景下,PImpl 模式可以在不改变类接口的前提下,修改类的实现细节,从而提高二进制兼容性。 如果你需要确保类的接口稳定,并希望在不改变接口的情况下能够自由地修改实现细节,PImpl 是一个合适的选择。

4 管理复杂实现:

当类的实现变得非常复杂时,将实现细节放在单独的实现类中,有助于保持代码的清晰和可维护性。

其他名字

Cheshire Cat:这个名字来源于《爱丽丝梦游仙境》中的柴郡猫,它有时会隐形,只留下笑脸。这个比喻形象地描述了 PImpl 模式隐藏实现细节的特性。
Compiler Firewall:这个名字强调了 PImpl 模式可以减少编译依赖,像防火墙一样隔离实现和接口。
d-pointer:这个名字主要在 Qt 库中使用,指的是 “指向数据(implementation)的指针”。

+---------------------+       +---------------------+
|      MyClass        |       |    MyClass::Impl    |
|---------------------|       |---------------------|
| - pImpl: Impl*      |       | - someData: int     |
|---------------------|       |---------------------|
| + MyClass()         |       | + Impl()            |
| + ~MyClass()        |       | + ~Impl()           |
| + someMethod()      |       | + someMethodImpl()  |
+---------------------+       +---------------------+
           |                                 |
           +-------------------+-------------+
                               |
                         has a |
                               |
                      +--------v---------+
                      |    std::unique_ptr|
                      |-------------------|
                      |     - Impl        |
                      +-------------------+

C++ 里的 “has a”

在 C++ 编程中,“has a” 关系指的是成员对象或成员指针的关系。也就是说,一个类拥有另一个类的对象或指针作为其成员。这种关系与继承(“is a”)不同:
“has a”(组合关系):一个类包含另一个类的对象或指针。
“is a”(继承关系):一个类继承自另一个类。
PImpl 模式中,“has a” 关系意味着 MyClass 包含一个 Impl 对象的指针,这个关系表示 MyClass 持有并管理 Impl 对象的生命周期。

示例代码

头文件:MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H

#include <memory> // 使用智能指针需要包含这个头文件

class MyClass {
public:
    MyClass(); // 构造函数
    ~MyClass(); // 析构函数

    void someMethod(); // 公有方法

private:
    // 前向声明一个名为 Impl 的类,这是实现细节类
    class Impl;
    // 使用 std::unique_ptr 管理 Impl 的指针
    std::unique_ptr<Impl> pImpl;
};

#endif // MYCLASS_H
源文件:MyClass.cpp
#include "MyClass.h" // 包含头文件

// 定义实现类 Impl
class MyClass::Impl {
public:
    // 实现方法
    void someMethodImpl() {
        // 具体的实现细节
        // 例如,这里我们简单地输出一段文字
        std::cout << "This is the implementation of someMethod." << std::endl;
    }
};

// 构造函数
MyClass::MyClass() : pImpl(std::make_unique<Impl>()) {
    // 初始化 pImpl,指向一个新的 Impl 对象
}

// 析构函数
MyClass::~MyClass() = default; // 使用默认析构函数,这里会自动释放 pImpl 指向的内存

// 公有方法,调用 Impl 类的实现方法
void MyClass::someMethod() {
    pImpl->someMethodImpl(); // 通过 pImpl 调用具体实现
}
主程序文件:main.cpp
#include "MyClass.h" // 包含 MyClass 类的头文件
#include <iostream> // 用于输出

int main() {
    MyClass myClass; // 创建 MyClass 对象
    myClass.someMethod(); // 调用公有方法
    return 0; // 程序结束
}

程序输出:

This is the implementation of someMethod.

说明

  1. 头文件 (MyClass.h)
    class MyClass 定义了一个类,其中有一个私有的指向实现类 Impl 的指针 pImpl
    构造函数和析构函数声明,以及一个公有方法 someMethod 的声明。

  2. 源文件 (MyClass.cpp)
    首先定义 MyClass 的实现类 Impl。这是一个具体实现类,其中包含方法 someMethodImpl
    MyClass::MyClass 构造函数初始化 pImpl,使用 std::make_unique 创建一个新的 Impl 对象。
    MyClass::~MyClass 析构函数默认删除 pImpl,因此不需要手动管理内存。
    MyClass::someMethod 方法调用 pImpl 指向的 Impl 对象的 someMethodImpl 方法。

  3. 主程序文件 (main.cpp)
    创建 MyClass 对象并调用其 someMethod 方法。

标签:实现,C++,PImpl,Idiom,MyClass,pImpl,Impl,指针
From: https://blog.csdn.net/flyfish1986/article/details/140395636

相关文章

  • C++ 面试宝典之:空类大小究竟是不是 0?
    以下内容为本人的学习笔记,如需要转载,请声明原文链接微信公众号「ENG八戒」https://mp.weixin.qq.com/s/pD4bIjX2kDzo8gbYRPktPQ首先,空类是什么?空类指的是不包含任何数据成员的类,但可能包含方法成员。实例化时,对象需要分配存储空间用于存放数据成员,数据成员的大小和数量......
  • 高质量C/C++编程指南总结(二)—— 文件版式
    1.空行在每个类声明之后、每个函数定义结束之后都要加空行。在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。2.代码行一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释。if、for、while、do等语句......
  • 高质量C/C++编程指南总结(一)—— 文件结构
    1.版权和版本的声明应位于头文件和定义文件的开头,主要包括的内容有:版本信息。文件名称、文件标识、摘要。当前的版本号、作者/修改者、完成日期。历史版本信息(取代版本、原作者、完成日期)。2.头文件结构为了防止头文件被重复引用,应当使用ifndef/define/endif结构产生......
  • 深入解析C++中的特殊成员函数:构造函数、析构函数、拷贝构造函数与赋值操作符
    深入解析C++中的特殊成员函数:构造函数、析构函数、拷贝构造函数与赋值操作符在C++编程的浩瀚宇宙中,构造函数、析构函数、拷贝构造函数和赋值操作符是构成对象生命周期和行为的四大基石。它们各自扮演着不可或缺的角色,确保了对象从创建到销毁,从复制到赋值的整个过程既安全又......
  • C++查找最大元素与s.find()和s.insert()
    题目描述:m老师在学习字符串的时候,对于字符串中的最大字符很感兴趣。因此他想对于输入的每个字符串,查找其中的ASCII码最大字母,在该字母后面插入字符串“(max)”。输入描述输入数据包括多个测试实例,第一行输入一个整数n表示样例个数。每个实例由一行长度不超过100的字符串......
  • C++ STL常用容器之vector(顺序容器)
    文章目录前言一、vector的介绍1.1vector的优点1.2vector的缺点1.3使用场景二、vector常用的操作2.1创建、初始化以及遍历容器2.2查询容器大小2.3访问容器中的元素2.4往容器中添加元素2.5删除容器中的元素2.6清空容器中的元素总结前言本文主要介绍C++STL......
  • C++ //练习 14.44 编写一个简单的桌面计算器使其能处理二元运算。
    C++Primer(第5版)练习14.44练习14.44编写一个简单的桌面计算器使其能处理二元运算。环境:LinuxUbuntu(云服务器)工具:vim 代码块/************************************************************************* >FileName:ex14.44.cpp >Author: >Mail: >C......
  • Android C++系列:Linux常用函数和工具
    1.时间函数1.1文件访问时间#include<sys/types.h>#include<utime.h>intutime(constchar*name,conststructutimebuf*t);返回:若成功则为0,若出错则为-1如果times是一个空指针,则存取时间和修改时间两者都设置为当前时间;如果times是非空指针,则存取时......
  • Android C++系列:Linux进程间关系
    1.终端在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端(ControllingTerminal),在前面文章我们说过,控制终端是保存在PCB中的信息,而我们知道fork会复制PCB中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端。默认情况......
  • C++ //练习 15.5 定义你自己的Bulk_quote类。
    C++Primer(第5版)练习15.5练习15.5定义你自己的Bulk_quote类。环境:LinuxUbuntu(云服务器)工具:vim 代码块/************************************************************************* >FileName:ex15.3.cpp >Author: >Mail: >CreatedTime:Fri12J......