PIMPL(Pointer to Implementation)本质上也属于设计模式的一种,PIMPL也称为 Opaque Pointer(不透明的指针)。主要目的是将一个类的 实现细节(private/protected 方法、成员) 和 其对外的公共接口 分离出来,使得 实现细节 可以在不影响客户端代码的情况下进行更改。
这样做的好处主要有以下两点:
- 对外隐藏实现细节,尤其是隐藏对外提供的库的实现细节
- 降低编译依赖,从而减少重新编译的时间
PIMPL实现的要点:
- 前向声明一个内部嵌套类Impl及其指针
- 将具体实现细节(private/protected 方法、成员)转移到内部嵌套类Impl中
隐藏实现细节
具体场景
在实际工作的过程中,需要对外提供一些库以及相应的头文件供三方人员使用,假设某个库的头文件如下:
// MyClass.h
#include <string>
#include <iostream>
class MyClass {
public:
MyClass();
~Myclass();
void publicMethod();
private:
void privateMethod();
private:
std::string mName_;
};
三方人员只需要使用到我们暴露的公共接口即可,但是我们却不得不将上述头文件提供给他们,这会导致如下两个问题:
- 实现细节被暴露,三方人员可以看到整个类的实现细节
- 接口不稳定,如果我们对mName_的类型进行改动,或者增加了一些成员变量,虽然没有修改public下的接口,但是对于使用人员来说也要更新头文件
- 增加了使用者的依赖,如2所说,使用者只关心自己调用的接口,不希望因为实现细节更改导致自己需要进行更改,显然当前提供的头文件无法满足
解决办法-PIMPL
PIMPL的引入就是为了解决上述问题,按照PIMPL的实现要点修改上述头文件如下:
// MyClass.h
#include <string>
#include <iostream>
class MyClass {
public:
MyClass();
~Myclass();
void publicMethod();
private:
struct Impl;
Impl* pImpl_;
};
对应的源文件如下:
#include "MyClass.h"
void Myclass::publicMethod() {
std::cout << "public method" << std::endl;
// 调用具体实现细节
pImpl_->privateMethod;
}
struct Myclass::Impl {
void privateMethod() {
std::cout << "private method" << std::endl;
}
std::string mName_;
}
Myclass::Myclass()
:pImpl_(new Impl)
{}
Myclass::~Myclass() {
delete pImpl_;
}
可以看出,修改后的代码 MyClass
的实现细节被封装在 MyClass::Impl
中,MyClass
的头文件只包含了公共接口;从而保证了:
- 不暴露实现细节
- 实现细节的改动不影响使用者,因为使用者的头文件中并不包含具体实现
降低编译依赖
降低编译依赖是由于编译器的工作方式以及C++的分离编译机制所决定的。在典型的C++项目中,每个源文件都会被独立地编译成目标文件(通常是.o
或.obj
文件)。这些目标文件之后由链接器(linker)将它们组合成可执行文件或库。
所以当我们对源文件的修改不涉及头文件中提供的公共接口,只需要重新编译源文件,而不需要重新编译使用者的代码;
举例如下:
假设使用者的代码编译结果为clien.o
, 当我们只修改了 MyClass.cpp
中的具体实现,例如增加了一个成员变量,而没有改变 MyClass.h
中的声明,那么只需要重新编译 MyClass.cpp
文件:
$ g++ -c MyClass.cpp -o MyClass.o
之后,你可以链接 Client.o
与新生成的 MyClass.o
:
$ g++ Client.o MyClass.o -o myProgram
这样就不必重新编译使用者的源代码Client.cpp
,因为它只依赖于 MyClass.h
的声明,而不依赖于 MyClass.cpp
中的实现。这是分离编译的优势之一。