前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布

pImpl

原创
作者头像
Rock_Lee
修改2020-10-14 10:47:16
8450
修改2020-10-14 10:47:16
举报
文章被收录于专栏:知识碎片知识碎片

为什么会用PIML

在C ++中,如果头文件类定义中的任何内容发生更改,则必须重新编译该类的,即使所更改是私有类成员。这是因为C ++的构建模型基于文本包含(textual inclusion),并且因为C ++假定调用者知道一个类的两项内容,而这两项可能会受到私有成员的影响:

  • 大小和布局:调用代码必须知道类的大小和布局,包括私有成员变量。这种实现的约束会导致更紧密地耦合调用方和被调用方,这是C ++对象模型和哲学的核心,因为保证编译器默认情况下直接访问对象是(也许是)必不可少的C ++实现其着名的高度优化效率的重要因素。
  • 函数:调用代码必须能够解析对类成员函数的调用,包括无法访问的、由非私有函数重载的私有函数,如果私有函数更好地匹配,则调用代码将无法编译。(出于安全原因,C ++做出了精心的设计决策,在进行可访问性检查之前执行了重载解析。例如,人们认为将功能的可访问性从私有更改为公共不应改变合法调用代码的含义。)

简介

PIMPL(Private Implementation 或 Pointer to Implementation),它将类的实现细节从对象表示中移除,放到一个分离的类中,并以一个不公开的指针进行访问:

是C++ 在构建导出库接口时特有的技术手段,优点

  1. 构造稳定的**ABI(application binary interface)**的C++库接口
  2. 减少编译时的依赖

注意ABI并不是API,

通常在C/C++中,API指的就是同应用程序或库一起公开的头文件,它包含各种公开的类型、变量、函数等。

而ABI通常指编译器在构建时应用程序时所需的细节:

  1. 数据类型的大小、布局和对齐;
  2. 调用约定(控制着函数的参数如何传送以及如何接受返回值),例如,是所有的参数都通过栈传递,还是部分参数通过寄存器传递;哪个寄存器用于哪个函数参数;通过栈传递的第一个函数参数是最先push到栈上还是最后;
  3. 系统调用的编码和一个应用如何向操作系统进行系统调用;
  4. 以及在一个完整的操作系统ABI中,目标文件的二进制格式、程序库等等。

一般class

代码语言:txt
复制
// file widget.h
//
class widget {
    // public and protected members
private:
    // private members; whenever these change,
    // all client code must be recompiled
};

PIMPL 如下:

代码语言:txt
复制
class widget {
    widget();
    ~widget();
    // public and protected members
private:
    class impl;
    std::unique_ptr<impl> pimpl;    // ptr to a forward-declared class
    };

class widget::impl{
    //private members; fully hidden, can be
    //changed at will without recompiling clients
}

widget::widget():pimpl{make_unique<widget::impl>(/*...*/)}
{
}
widget::~widget() = defalut;

避免使用原生指针和显式的delete。要仅使用C ++11最合适的选择是通过unique_ptr来保存Pimpl对象。

每个widget对象都动态分配其impl对象,即不透明的指针pimpl。这样打破了调用者对私有细节的依赖性,包括打破编译时依赖性二进制依赖性

  • 不需要为客户端代码定义仅在类的实现中提到的类型,这可以消除多余的#include 并提高编译的速度
  • 可以 更改类的实现,即可以在impl中自由添加或删除私有成员,而无需重新编译客户端代码。这是提供ABI-safety或二进制兼容性的有用技术,因此客户端代码不依赖于对象的确切布局。

但这样也会带来性能上 的损失:

  • 每个construction/destruction必须allocate/deallocate memory
  • 隐藏成员的每次访问都可能至少需要一次额外的间接访问(如果要访问的隐藏成员本身使用后向指针调用可见类中的函数,这样会造成多次的间接访问,但通常很容易避免需要后向指针)

类的哪些部分可以放入impl对象?

所有的private members? 并不是。原因:

  1. 即使虚拟函数是私有的,您也无法在Pimpl中隐藏虚拟成员函数。 如果虚函数覆盖了从基类继承的虚函数,则它必须出现在实际的派生类中
  2. 如果Pimpl中的函数需要依次使用可见函数,则它们可能需要指向可见对象的“后向指针”,这又增加了一个间接层次。 通常最好的折衷方法是放入私有成员,并仅将那些需要由私有函数调用的非私有函数放入Pimpl。

参考文档

difference-between-api-and-abi

Herb Sutter(C++标准委员会成员)一些相关的博客

GotW #7a Solution: Minimizing Compile-Time Dependencies, Part 1

[GotW #7a Solution: Minimizing Compile-Time Dependencies, Part 2

Compilation Firewalls (Difficulty: 6/10)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么会用PIML
  • 简介
  • 类的哪些部分可以放入impl对象?
  • 参考文档
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档