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 时常见的陷阱。
挑战
简单让我们使用 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:读取两个输入:- 一个消息字符串(可以包含空格)
- 一个加密密钥(整数)
创建一个
SecureMessage对象,设置密钥,并显示结果:- 输出
Original:,后接原始消息 - 输出
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;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C++ 在线编译器