一、Pimpl是什么
Pimpl全称是point to implementation,译为指向实现的指针;是一种c++程序技巧。 其具有如下基本形式:
class widget {
private:
struct impl;
impl* pimpl_;
};
二、为什么需要Pimpl技术
我们知道,在C/C++中,声明变量是不占用内存的,只有定义变量时,编译器才会为相应的变量分配对应的内存;对于一个C++的类class来说,也是如此。当在类内部定义成员变量时,会影响整个类的内存大小等相关因素;因此,若修改了类中的相关定义实现,即使该实现是private成员这是或者private函数,那么包含这个类的所有用户代码都会被重新编译;因为这些代码依赖于当前类的实现细节。 因此,为了减小编译时的依赖,就可以运用这种Pimpl技术;从其基本的定义形式就可以看出,其大小固定为一个指针大小,而指针所指对象的变化并不影响使用该指针的类的大小,这样一来,用这个impl指针来包裹类widget中容易变动的实现细节,在更改impl对象时,并不影响外部用户代码的编译;其可以带来两个好处:
- 第一,打破编译依赖,可以使整个系统代码编译速度更快;
- 第二,其隐藏了内部实现细节的同时,也提高了代码修改的灵活度。
三、怎么使用Pimpl技术
为了使用Pimpl技术,其基本的使用形式如下:
class widget {
public:
widget();
~widget();
private:
class impl;
unique_ptr<impl> pimpl;
};
class widget::impl {
};
widget::widget() : pimpl{ new impl{ } } { }
widget::~widget() { }
其使用细节准则如下:
- (1)使用std::unique_ptr来管理相应的pimpl指针,这是为了更好的内存管理,避免未翻放相应内存;更详细的原理,可以查阅《Effective C++》第三版的条款13项,以对象管理资源的理念;
- (2)在.cpp文件中定义实现widget::impl类,以达到隐藏实现细节和减少编译依赖的目的;
- (3)在.cpp文件中,要显式定义widget::widget和widget::~widget函数,避免编译器默认生成;同样的,对于拷贝构造和赋值构造,若不想被使用,应该申明为私有;若需要被使用,也要显式定义。
- (4)将widget中所有私有的非虚成员放入impl中去定义和实现;这也是符合OOP编程思想的理念;在OOP编程中,public成员,是为了给外部代码使用;protect和virtual成员是为了给子类继承和修改;而其它私有非虚成员,则是类的实现细节,不影响对外暴露;
- (5)若impl类中需要访问widget的public成员,则将widget对象作为impl的成员函数的参数,进行传递;一般采用widget指针或者widget引用参数;
四、优缺点总结
优点:
- 解除了接口和实现之间的偶合,隐藏了实现细节;
- 减少了编译依赖性,提高编译速度;
- 更高修改实现的灵活度,而不用考虑外部用户代码。
缺点:
- widget的public成员函数需要通过impl指针,进一步访问对象数据,添加了一层间接性;
- 每一个widget对象,相比于最原始实现,至少多消耗一个impl指针大小的内存;
- widget对象必须初始化impl指针成员,动态分配impl对象;因此,也增加了动态内存分配内存(及后续释放内存)的额外开销;
参数文献: https://en.cppreference.com/w/cpp/language/pimpl PImpl说明 https://herbsutter.com/gotw/_100/ Compilation Firewalls; 《Effective C++》第三版,条款13,条款31等;
|