背景资料:
所述PIMPL成语(指针实现)是用于执行隐藏在其中一个公共类包装的结构或类,可以不在库的公共类是的一部分外部看到的技术.
这会隐藏来自库用户的内部实现细节和数据.
在实现这个习惯用法时,为什么要将公共方法放在pimpl类而不是公共类上,因为公共类方法实现会被编译到库中,而用户只有头文件?
为了说明,此代码将Purr()
实现放在impl类上并将其包装起来.
为什么不直接在公共类上实现Purr?
// header file:
class Cat {
private:
class CatImpl; // Not defined here
CatImpl *cat_; // Handle
public:
Cat(); // Constructor
~Cat(); // Destructor
// Other operations...
Purr();
};
// CPP file:
#include "cat.h"
class Cat::CatImpl {
Purr();
... // The actual implementation can be anything
};
Cat::Cat() {
cat_ = new CatImpl;
}
Cat::~Cat() {
delete cat_;
}
Cat::Purr(){ cat_->Purr(); }
CatImpl::Purr(){
printf("purrrrrr");
}
Rob Wells.. 64
我想大多数人都把它称为Handle Body成语.请参阅James Coplien的书"高级C++编程风格和成语"(亚马逊链接).它也被称为柴郡猫,因为刘易斯卡罗尔的角色逐渐消失,直到只剩下咧嘴笑.
示例代码应分布在两组源文件中.然后只有Cat.h是产品附带的文件.
cat.cmpl包含CatImpl.h,CatImpl.cpp包含CatImpl :: Purr()的实现.使用您的产品时,公众无法看到此信息.
基本上,这个想法是尽可能地隐藏执行方式.如果您的商业产品是作为一系列库提供的,那么这将非常有用,这些库可以通过API访问,客户代码可以编译并链接到该API.
我们在2000年重写了IONAs Orbix 3.3产品.
正如其他人所提到的,使用他的技术完全将实现与对象的接口分离.然后,如果您只想更改Purr()的实现,则不必重新编译使用Cat的所有内容.
这种技术用于合同设计的方法中.
我想大多数人都把它称为Handle Body成语.请参阅James Coplien的书"高级C++编程风格和成语"(亚马逊链接).它也被称为柴郡猫,因为刘易斯卡罗尔的角色逐渐消失,直到只剩下咧嘴笑.
示例代码应分布在两组源文件中.然后只有Cat.h是产品附带的文件.
cat.cmpl包含CatImpl.h,CatImpl.cpp包含CatImpl :: Purr()的实现.使用您的产品时,公众无法看到此信息.
基本上,这个想法是尽可能地隐藏执行方式.如果您的商业产品是作为一系列库提供的,那么这将非常有用,这些库可以通过API访问,客户代码可以编译并链接到该API.
我们在2000年重写了IONAs Orbix 3.3产品.
正如其他人所提到的,使用他的技术完全将实现与对象的接口分离.然后,如果您只想更改Purr()的实现,则不必重新编译使用Cat的所有内容.
这种技术用于合同设计的方法中.
因为你希望Purr()
能够使用私人成员CatImpl
. Cat::Purr()
没有friend
声明就不允许这样的访问.
因为你不混合责任:一个类实现,一个类转发.
值得的是,它将实现与界面分开.这在小型项目中通常不是很重要.但是,在大型项目和库中,它可用于显着减少构建时间.
考虑到Cat
可能包含许多头的实现可能涉及模板元编程,这需要花费时间自行编译.为什么一个只想使用它的用户Cat
必须包括所有这些?因此,使用pimpl习语(因此是前向声明CatImpl
)隐藏所有必需的文件,并且使用该接口不会强制用户包含它们.
我正在开发一个非线性优化库(读"很多讨厌的数学"),它在模板中实现,因此大多数代码都在标题中.编译大约需要五分钟(在一个体面的多核CPU上),只需解析一个空的头文件.cpp
大约需要一分钟.所以使用该库的任何人每次编译代码都需要等待几分钟,这使得开发非常繁琐.但是,通过隐藏实现和标头,只需包含一个简单的接口文件,即可立即编译.
它不一定与保护实现不被其他公司复制有任何关系 - 除非你的算法的内部工作可以从成员变量的定义中猜到(如果是这样,它是可能不是很复杂,一开始就不值得保护).
如果您的类使用了pimpl习惯用法,则可以避免更改公共类中的头文件.
这允许您向pimpl类添加/删除方法,而无需修改外部类的头文件.你也可以在pimpl中添加/删除#includes.
当您更改外部类的头文件时,您必须重新编译#includes它的所有内容(如果其中任何一个是头文件,您必须重新编译#includes它们的所有内容,依此类推)
通常,在Owner类的头部(本例中为Cat)中对Pimpl类的唯一引用将是一个前向声明,正如您在此处所做的那样,因为这可以大大减少依赖性.
例如,如果您的Pimpl类将ComplicatedClass作为成员(而不仅仅是指针或对它的引用),那么您需要在使用之前完全定义ComplicatedClass.在实践中,这意味着包括"ComplicatedClass.h"(它也将间接包括ComplicatedClass所依赖的任何内容).这可能会导致单个标题填充大量内容,这对于管理依赖项(以及编译时间)是不利的.
当你使用pimpl idion时,你只需要#include你所有者类型的公共接口中使用的东西(这里是Cat).这使得使用你的图书馆的人的事情变得更好,这意味着你不需要担心人们依赖于图书馆的某些内部部分 - 要么是错误的,要么是因为他们想要做一些你不允许的事情,所以他们#define包含您的文件之前的私人公众.
如果它是一个简单的类,通常没有理由使用Pimpl,但是对于类型非常大的时候,它可以是一个很大的帮助(特别是在避免长构建时间)