pImpl
为什么会用PIML
在C ++中,如果头文件类定义中的任何内容发生更改,则必须重新编译该类的,即使所更改是私有类成员。这是因为C ++的构建模型基于文本包含(textual inclusion),并且因为C ++假定调用者知道一个类的两项内容,而这两项可能会受到私有成员的影响:
- 大小和布局:调用代码必须知道类的大小和布局,包括私有成员变量。这种实现的约束会导致更紧密地耦合调用方和被调用方,这是C ++对象模型和哲学的核心,因为保证编译器默认情况下直接访问对象是(也许是)必不可少的C ++实现其着名的高度优化效率的重要因素。
- 函数:调用代码必须能够解析对类成员函数的调用,包括无法访问的、由非私有函数重载的私有函数,如果私有函数更好地匹配,则调用代码将无法编译。(出于安全原因,C ++做出了精心的设计决策,在进行可访问性检查之前执行了重载解析。例如,人们认为将功能的可访问性从私有更改为公共不应改变合法调用代码的含义。)
简介
PIMPL(Private Implementation 或 Pointer to Implementation),它将类的实现细节从对象表示中移除,放到一个分离的类中,并以一个不公开的指针进行访问:
是C++ 在构建导出库接口时特有的技术手段,优点:
-
构造稳定的**
ABI(application binary interface)
**的C++库接口 - 减少编译时的依赖
注意ABI并不是API,
通常在C/C++中,API指的就是同应用程序或库一起公开的头文件,它包含各种公开的类型、变量、函数等。
而ABI通常指编译器在构建时应用程序时所需的细节:
- 数据类型的大小、布局和对齐;
- 调用约定(控制着函数的参数如何传送以及如何接受返回值),例如,是所有的参数都通过栈传递,还是部分参数通过寄存器传递;哪个寄存器用于哪个函数参数;通过栈传递的第一个函数参数是最先push到栈上还是最后;
- 系统调用的编码和一个应用如何向操作系统进行系统调用;
- 以及在一个完整的操作系统ABI中,目标文件的二进制格式、程序库等等。
一般class
// file widget.h
//
class widget {
// public and protected members
private:
// private members; whenever these change,
// all client code must be recompiled
};
PIMPL 如下:
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? 并不是。原因:
- 即使虚拟函数是私有的,您也无法在Pimpl中隐藏虚拟成员函数。 如果虚函数覆盖了从基类继承的虚函数,则它必须出现在实际的派生类中
- 如果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)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- docker-compose搭建redis集群之哨兵模式
- docker-compose搭建redis集群之主从复制
- c/c++补完计划(七): 哨兵节点
- ASP.NET Core 奇技淫巧之接口代理转发
- SpringBoot 通过注解的方式整合 Mybatis + PageHelper 分页显示
- 将BX中的数以二进制形式在屏幕上显示出来。
- String及StringTable(二):java中的StringTable
- 设在起始地址为STRING的存储空间存放了一个字符串(该串已存放在内存中,无需输入,且串长不超过99),统计字符串中字符“A”的个数,并将结果显示在屏幕上。
- Docker容器ElasticSearch-Head创建索引无响应406
- springboot监控&springboot配置https
- 面试中最长常问到的 HashMap,你都知道多少?
- spring security 使用自定义AuthenticationFailureHandler无法跳转failureUrl
- Android studio 下载安装教程和第一个程序运行最新,多图详解
- ubuntu16.04下qt5.14报错:/home/zhangfakai/Qt5.14.1/5.14.1/gcc_64/include/QtGui/qopengl.h:141: error: GL/
- 每天手撕一道算法-64. 最小路径和