当前位置:  开发笔记 > 编程语言 > 正文

为什么要使用"PIMPL"成语?

如何解决《为什么要使用"PIMPL"成语?》经验,为你挑选了5个好方法。

背景资料:

所述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的所有内容.

这种技术用于合同设计的方法中.



1> Rob Wells..:

我想大多数人都把它称为Handle Body成语.请参阅James Coplien的书"高级C++编程风格和成语"(亚马逊链接).它也被称为柴郡猫,因为刘易斯卡罗尔的角色逐渐消失,直到只剩下咧嘴笑.

示例代码应分布在两组源文件中.然后只有Cat.h是产品附带的文件.

cat.cmpl包含CatImpl.h,CatImpl.cpp包含CatImpl :: Purr()的实现.使用您的产品时,公众无法看到此信息.

基本上,这个想法是尽可能地隐藏执行方式.如果您的商业产品是作为一系列库提供的,那么这将非常有用,这些库可以通过API访问,客户代码可以编译并链接到该API.

我们在2000年重写了IONAs Orbix 3.3产品.

正如其他人所提到的,使用他的技术完全将实现与对象的接口分离.然后,如果您只想更改Purr()的实现,则不必重新编译使用Cat的所有内容.

这种技术用于合同设计的方法中.


在合同设计中如何使用pimpl成语(或称为句柄体成语)?

2> Xavier Nodet..:

因为你希望Purr()能够使用私人成员CatImpl. Cat::Purr()没有friend声明就不允许这样的访问.

因为你不混合责任:一个类实现,一个类转发.


尽管如此,这仍然是一种痛苦.但话说回来,如果它是一个库类,那么方法不应该改变太多.我正在寻找的代码似乎走在安全的道路上,到处都是使用pimpl.

3> the swine..:

值得的是,它将实现与界面分开.这在小型项目中通常不是很重要.但是,在大型项目和库中,它可用于显着减少构建时间.

考虑到Cat可能包含许多头的实现可能涉及模板元编程,这需要花费时间自行编译.为什么一个只想使用它的用户Cat必须包括所有这些?因此,使用pimpl习语(因此是前向声明CatImpl)隐藏所有必需的文件,并且使用该接口不会强制用户包含它们.

我正在开发一个非线性优化库(读"很多讨厌的数学"),它在模板中实现,因此大多数代码都在标题中.编译大约需要五分钟(在一个体面的多核CPU上),只需解析一个空的头文件.cpp大约需要一分钟.所以使用该库的任何人每次编译代码都需要等待几分钟,这使得开发非常繁琐.但是,通过隐藏实现和标头,只需包含一个简单的接口文件,即可立即编译.

它不一定与保护实现不被其他公司复制有任何关系 - 除非你的算法的内部工作可以从成员变量的定义中猜到(如果是这样,它是可能不是很复杂,一开始就不值得保护).



4> Nick..:

如果您的类使用了pimpl习惯用法,则可以避免更改公共类中的头文件.

这允许您向pimpl类添加/删除方法,而无需修改外部类的头文件.你也可以在pimpl中添加/删除#includes.

当您更改外部类的头文件时,您必须重新编译#includes它的所有内容(如果其中任何一个是头文件,您必须重新编译#includes它们的所有内容,依此类推)



5> Wilka..:

通常,在Owner类的头部(本例中为Cat)中对Pimpl类的唯一引用将是一个前向声明,正如您在此处所做的那样,因为这可以大大减少依赖性.

例如,如果您的Pimpl类将ComplicatedClass作为成员(而不仅仅是指针或对它的引用),那么您需要在使用之前完全定义ComplicatedClass.在实践中,这意味着包括"ComplicatedClass.h"(它也将间接包括ComplicatedClass所依赖的任何内容).这可能会导致单个标题填充大量内容,这对于管理依赖项(以及编译时间)是不利的.

当你使用pimpl idion时,你只需要#include你所有者类型的公共接口中使用的东西(这里是Cat).这使得使用你的图书馆的人的事情变得更好,这意味着你不需要担心人们依赖于图书馆的某些内部部分 - 要么是错误的,要么是因为他们想要做一些你不允许的事情,所以他们#define包含您的文件之前的私人公众.

如果它是一个简单的类,通常没有理由使用Pimpl,但是对于类型非常大的时候,它可以是一个很大的帮助(特别是在避免长构建时间)

推荐阅读
携手相约幸福
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有