Pimpl (pointer to implementation) 是一种将接口和具体的实现分开的一种方式,优化编译时间,保护封装实现的一种经验技巧。
tl;dr
就是说模块对外接口类里面不放实现,而是保存一个指针, 指向真正实现的类。然后在接口类里面同名方法通过指针调用真正实现方法。
原理
通过指针的间接调用,实现消息的转发,使得行为与原来行为相同。
Demo
普通类的设计
假设原来有这样一个类:
1 | // File: Foo.h |
1 | // File: Foo.cpp |
Pimpl模式
在Foo的基础上在抽象出一层接口:
1 | // File: Foo.h |
1 | // File: Foo.cpp |
1 | // File: Foo_impl.h |
1 | // File: Foo_impl.cpp |
优缺点:
优点:
- 优化编译速度, 如上面的例子, 普通的实现方式,在设计类时不可避免要使用一些其他的类或者变量,就会引入很多头文件。那么在编译时,一个头文件的引入可能就会增加一杯咖啡的时间(例如windows.h), 而Pimpl不会, 因为其他模块在编译时只需要引入Foo.h头文件即可, 里面之后告诉编译器有个FooImpl的类, 没有很复杂的东西, 这时就不会引入很多的东西,编译时间就会短很多。
- 优化构建过程,无论我们怎么修改FooImpl, 只要Foo.h不变,那么就不会影响到使用的模块,编译时只需要编译FooImpl部分即可。
- 对具体实现的隐藏,模块输出对外接口时,头文件只会看到一个FooImpl的指针, 看不到类里面的内容。
- 对单元测试友好, 测试时只需要测试Impl部分即可, 因为Foo只是个接口。 如果测试另一个模块,Foo也可以加一个Only for Test的构造,来模拟真实的FooImpl, 避免过度耦合无法测试, 例如socket, 很多人喜欢在负责传输的模块内部直接写一个socket, 这样在测试时很难去模拟网络IO, 用Impl就很好的解决了这个问题。
缺点:
- 写起来更麻烦, 写的更多了, 但这好像不是什么太大的问题
- 增加开销, 因为多了一层调用
- 增加了内存的开销, 多了一个智能指针
参考:
Effective C++ & More Effective C++