Menu
CoddyTech

Pimpl 惯用法

本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 78 课,共 104 课。

Pimpl 惯用法(指向实现的指针)是一种将类的实现细节移到单独的前置声明类中,从而隐藏这些细节的技术。这样可以减少编译依赖,并让私有成员真正隐藏在头文件之外。

核心思路很简单:你不在类中直接声明私有成员,而是声明一个指向实现类的指针,这个实现类只在源文件中定义:

// Widget.h
#include <memory>

class Widget {
public:
    Widget();
    ~Widget();
    void doSomething();
    
private:
    class Impl;  // 前向声明
    std::unique_ptr<Impl> pImpl;
};
// Widget.cpp
#include "Widget.h"
#include <iostream>

class Widget::Impl {
public:
    int data = 42;
    void process() { std::cout << "Processing: " << data << "\n"; }
};

Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;

void Widget::doSomething() { pImpl->process(); }

主要优点是编译防火墙和二进制兼容性。更改 Impl 类时,只需重新编译源文件,无需重新编译每个包含该头文件的文件。这能大幅缩短大型项目的构建时间。

请注意,析构函数必须在源文件中定义(即使使用默认定义),因为 unique_ptr 需要 Impl 的完整类型才能将其删除。这是初次使用 Pimpl 时常见的陷阱。

challenge icon

挑战

简单

让我们使用 Pimpl 惯用法构建一个安全消息处理器,将加密实现细节隐藏在头文件中。这展示了 Pimpl 如何创建编译防火墙:任何包含你的头文件的人都看不到消息处理的内部实现。

你将把代码分成 3 个文件:

  • SecureMessage.h:定义 SecureMessage 类的公共接口。

    你的类应该有一个前置声明的 Impl 类,以及一个指向它的 std::unique_ptr。公共接口应包括:

    • 一个接收原始消息 const std::string& 的构造函数
    • 一个析构函数(必须在此处声明,在 .cpp 文件中定义)
    • 一个用于设置加密密钥的 setKey(int key) 方法
    • 一个返回加密后消息的 getEncrypted() 方法,返回值为 std::string
    • 一个返回原始消息的 getOriginal() 方法

    头文件应该只展示公共接口:这里不应显示加密工作原理的实现细节。

  • SecureMessage.cpp:定义嵌套的 Impl 类并实现所有方法。

    你的 Impl 类应该存储原始消息和加密密钥(默认为 0),并处理实际的加密逻辑。使用简单的凯撒密码进行加密:将每个字符按密钥值进行偏移。例如,密钥为 3 时,'a' 会变成 'd','z' 会循环到 'c'。

    加密只应影响小写字母(a-z),其他所有字符都保持不变。记得在此处定义析构函数(即使使用默认定义也可以),因为 unique_ptr 需要完整的 Impl 类型。

  • main.cpp:读取两个输入:
    1. 一个消息字符串(可以包含空格)
    2. 一个加密密钥(整数)

    创建一个 SecureMessage 对象,设置密钥,并显示结果:

    1. 输出 Original: ,后接原始消息
    2. 输出 Encrypted: ,后接加密后的消息

例如,输入 hello world 和 3 时:

Original: hello world
Encrypted: khoor zruog

输入 xyz abc 和 5 时:

Original: xyz abc
Encrypted: cde fgh

注意,头文件没有透露凯撒密码的实现细节,这就是 Pimpl 的优势。如果你之后改用其他加密算法,只需重新编译 SecureMessage.cpp,而无需重新编译任何包含该头文件的文件。

亲手试试

#include <iostream>
#include <string>
#include "SecureMessage.h"

int main() {
    // 读取消息(可能包含空格)
    std::string message;
    std::getline(std::cin, message);
    
    // 读取加密密钥
    int key;
    std::cin >> key;
    
    // TODO: 用消息创建一个 SecureMessage 对象
    
    // TODO: 设置加密密钥
    
    // TODO: 输出 "Original: ",然后输出原始消息
    
    // TODO: 输出 "Encrypted: ",然后输出加密后的消息
    
    return 0;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C++ 在线编译器