适当 提取 class (template) 定义 和 function (template) 声明,
是花费心力 最多的 2件事, 但 完成它们,
实现 大多就 直接了当
1 尽可能 postpone/延后 object definition
1 `延后 对象 定义, until 必须使用 对象 前一刻`
`对象 定义过早`
|
| 如 因
|/
————————————————————————————————————————————
避免
————————————————————————————————————————
[1] 构造/析构 不必要的 对象
可能因 `抛出异常, 而 没被 真正使用`
————————————————————————————————————————
[2] `毫无意义 的 default 构造`
————————————————————————————————————————————
2 `真正动作 提取到 函数`
3 `构造时 初始化 效率高` 于 `default 构造 + 赋值`
|
| 可能
|/
毫无意义
4 `对象 只在 循环内 使用, 定义于 循环内还是外 更好?`
外: 1 default ctor + 1 dtor + n assignment
内: n ctor + n dtor
2 少 转型
转型(cast) 破坏了 type system, 可能导致 `任何种类 的 麻烦`
1 类型转换之 `新/旧 style
——————————————————————————————————————————————————————————————————
old-style
C 风格
(T) expression
函数风格
T (expression) // T 可为 C++ 中 class
——————————————————————————————————————————————————————————————————
new-style / C++ style
——————————————————————————————————————————————————————————————
const_cast<T>( expression )
`去除 常量性`
——————————————————————————————————————————————————————————————
static_cast<T>( expression )
强迫 隐式转换
`无法实现 去除常量性: const obj -> non-const obj`
——————————————————————————————————————————————————————————————
dynamic_cast<T>( expression )
安全 向下转型
唯一无法由 old-style cast 实现的 new-style cast
——————————————————————————————————————————————————————————————
reinterpret_cast<T>( expression )
欲 低级转型, 不可移植
——————————————————————————————————————————————————————————————————
new-style 更受欢迎的原因:
1) code 易 被 (人工 / grep 等 工具 ) 辩识
2) 转型目标越窄, compiler 越可能诊断出错误
2 `唯一需 旧式转型 的 时机: 单 para explicit ctor`
3 `显式转型 未必需要`
4 类型转换 往往令 `compiler 编译出 运行期 执行的 code`
`对象 ( Derived )` 可能拥有 `> 1个 地址`
以 Base*/Derived* 指向
offset 在 运行期 被施行于 Derived* 身上, 以 取得 正确的 Base*
=> `不要假设 对象 在 C++ 中 如何布局`
`除非 从 某个 compiler 说明文档里 明确得知`
5 对象 `转型结果` 是 `被转 part` 的 `临时 copy`
`derived class 内 vf 第1个动作 是调用 base class 相应函数`
如 static_cast<Base>(*this).vf
对 *this 对象之 `base part 的 临时 copy 调 vf`
Note
`base part 没被改, derived part 被改`
=> `当前对象` 成 `伤残` 状态
|
| 解决
|/
Base class name 限定
6 `dynamic_cast 之 低效 / 可能需要的场景 / 可能的 替代方案 / 必须避免 的 场景`
(1) 低效
至少有1个 实现 基于 `class name 之 字符串比较`
=> n 层单继承体系
可能需 n 次 strcmp 调用: 比较 class name
(2) 可能需要的场景
————————————————————————————————————————————————————————————————————————
[1] `多重继承 之 恢复接口`
[2] `用 non-virtual function + base class ptr/ref 处理 继承体系 中 obj`
————————————————————————————————————————————————————————————————————————
(3) 可能的 替代方案
[1] 用 vf + base class 中 vf 用 缺省实现 (如 do nothing)`
+ 各 Derived class 的 vf override
[2] 用 多个容器, 每个 放 一种 派生类 SP
(4) 必须避免 的 场景: 连串/if-else dynamic_cast
继承体系 变, client code 就要变
3 避免返回 handles to object internals
`handle: ref / ptr / iterator`
用来 访问 对象
1 `return 指向 internal handle`
————————————————————————————————————————————————————————————————————————
3 大风险
————————————————————————————————————————————————————————————————————
[1] `封装性` 降低
Note: `不该 令 mem func 返回的 handle 指向 访问权限更严的 mem`
原因: 该 mem 访问权限 放松为 和 mem func 一样
如: public mem func 却 return hanlde 指向 private mem data / func
=> mem data / func 的 访问权限由 private 放松为 public
|
| 优化
|/
`蓄意` + `有限度` 地 `放松封装性`
| |
| |/
| 2] return handle to const => 不允许修改
|/
1] 愿意让 client 看到 对象 internal
————————————————————————————————————————————————————————————————————
[2] const mem func +
`return handle to non-const` 可 `改变 对象状态`
|
| 如
|/
non-const 引用
————————————————————————————————————————————————————————————————————
[3] handle `lifetime 长于 所指 对象`
`handle 悬挂`
|
指向 不再存在的对象
————————————————————————————————————————————————————————————————————
2 有时必须 让 mem func 返回 internal handle
STL 容器: string / vector
下标运算符 operator[] 返回 ref指向 `容器内数据`
4 为 exception-safe 尽力
1 `抛出异常` 时 `3 种 保证`
(1) `基本承诺`
不败坏数据 + `程序状态 任意(半构造状态)`
(2) `强烈保证`
`程序状态 不变`
`失败 时 回滚` 到 之前状态
(3) `nothrow 保证: 承诺 绝不抛出异常
内置类型 上操作
空白异常明细 empty exception specification // int f() throw();
不是指 不抛出异常
而是指 `抛出异常 时, 会有 意想不到的结果`
2 `异常安全 2 个条件`
[1] `不 泄漏资源`
|
| 场景
|/
new 抛出异常 + `unlock 被跳过`
=> mutex 永远被锁住
|
| 解决
|/
`资源管理 class ( RAII 类 ) Lock: 确保 mutex 被及时释放`
[2] `不 败坏数据`
|
| 场景
|/
`delete p + count 递增 + new 抛出异常
=> p 指向 已删除对象
|
|
|/
[1] `raw pointer -> shared_ptr ( 其实也属于 资源管理: 避免 delete 被跳过 )
[2] 重排语句次序: lock -> 操作 ( new ) -> ++
3 `强烈保证: copy and swap`
`copy 欲 修改对象` -> `修改 副本` -> 在 不抛出异常的 `swap 中 置换 原件 和 副本`
+ `pImpl 手法`
[1] 将 `属于对象 的 data` 从 `源对象 放到 另一对象`
[2] 用 struct 而不是 class: 封装性 已由 spImpl 是 private 而获得
5 深入理解 inlining
`inlining: 编译期` 将 `函数调用` 替换为 `函数本体`
1 inline 函数 `优缺点`
(1) 优
[1] 比 `宏` 好
[2] 无 函数调用 之 `额外开销`
[3] 让 compiler 有 `编译优化` 机会
(2) 缺
[1] `代码膨胀`
函数本体 比 函数调用 的 compiler 产出码 多
[2] `换页 paging -> 效率损失`
inline 过多
2 `inline vs. template`
(1) (通常) 均放 `头文件`
原因
`编译期行为`
compiler 编译期 `必须知道 inline 函数 / template 长什么样子`
`(2) template 实例化 与 inlining 无关`
若想 `template 所有可能 实例化 版本
都 inline`, 才 将 template 声明为 inline
3 `compiler 拒绝 inline 的 case`
[1] 函数太复杂
[2] vf
virtual: 等到 运行期 才确定 调用哪个函数
inline : 执行前 先将 函数调用 替换为 函数本体
两者矛盾
[3] `函数指针` 之 函数调用
[4] `ctor / dtor`
6 最小化 文件 间 编译依赖
含 `实现细节` 不含 `实现细节 ( 数据 )`
|\ |\
| |
| |
`定义式` 必须 `直接` 或 `间接` 列出 实现细节`
|\
|
|
1 `class 定义文件` 与 `#include 文件` 的 `编译依赖`
|
| 若 含
|/
[1] class 定义 的 实现细节
=> 实现细节 变, 则 class 的 client 要变
| |
| 如 | class 定义 文件
|/ |/
成员 Date Interface(类)
[2] #include 间 可能 `连串 依赖`
[3] #include 标准库 头文件 不太可能成 编译瓶颈
(1) `class 定义式` 含 `实现细节`
=> `修改 class 的 implement` 时, class 的 client 就` 需要 `重新编译`
(2) `class 定义式` 不含 `实现细节`
`修改 class 的 interface 时, class 的 client 才` 需要 `重新编译`
`pImpl` 手法 实现 真正的 `接口 与 实现 分离`
2 `最小化 编译依赖`
本质
`声明 的 依赖性` 替换 `定义 的 依赖性`
头文件 应 尽可能 `自我满足`, 万一做不到,
则 尽量依赖 other 文件内 声明式 (而非 定义式)
方法
[1] `用 ref/ptr` 能完成 任务, 就不要用 object
[2] `尽量用 class 声明式`, 除非 `真正需要 class 定义式`
`by value` 方式 `传参 或 返回值` 并 `不需要 class 定义式`
原因: `client 调用 function 前, 必先曝光( 实现出 ) class 定义式`
class Date;
Date today();
void clearAppointment(Date d);
[3] `为 声明式 和 定义式 提供不同的 头文件`
// 3 Date 的 client.cpp
#include "dateDecl.h" // 而不是 #include "date.h"
Date today();
void clearAppointment(Date d);
3 2 种 Handle class
3.1 `Handle class & HandleImpl class`
将其 所有函数 `转交` 给相应 `实现类 的 相应函数`
两者 成员函数 接口完全相同
(1) 文件结构
————————————————————————————————————————————————————————————————————————————————————————————————————————————
1 Interface.h Interface 类定义式
————————————————————————————————————————————————————————————————————————————————————————————————————————————
2 InterfaceImpl.h InterfaceImpl 类定义式
————————————————————————————————————————————————————————————————————————————————————————————————————————————
3 Interface.cpp Interface 实现文件
Interface 成员函数 `转交` 给 InterfaceImpl 成员函数
Interface::Interface(const std::string& name, const Data& birthday) : spImpl(
new InterfaceImpl(name, birthday)
) {}
std::string Interface::name() const {
return spImpl->name(); }
————————————————————————————————————————————————————————————————————————————————————————————————————————————
3.2 `Interface class 即 abstract base class `
(1) 无 成员变量 + ctor
只有 1个 virtual dtor + 一组 pure virtual 函数,
描述 derived class 的 接口
Note
C++ Interface class / abstract base class 与 Java 中 Interface 区别:
C++ Interface class 中
`允许实现 成员变量 和 成员函数
|
|
|/
non-virtual function
|
| 如
|/
Factory Method: static mem func
(2) `client 必须 以 Interface class 的 ptr / ref 写程序`
原因: `无法` 对 `内含 pure vf` 的 Interface class `构造 新对象`
(3) `client 必须` 能为 相应 `derived class 创建对象`
`Factory Method`, 扮演 `真正将被 实例化` 的 `derived class` 的 `ctor` 的角色
|
|
|/
[1] 也叫 virtual ctor
[2] 通常是 static mem func
`支持 Interface class 接口 的`
`Implement class 必须被定义 出来` + `真正的 ctor 必须被调用`
|
| 这一切都在 `Factory Method 实现文件` 中 发生
|
|/
Interface 的 Derived 类
(4) 源文件结构
————————————————————————————————————————————————————————————————
1 client.cpp: 创建 对象, 支持 Interface 接口
————————————————————————————————————————————————————————————————
2 Interface.h: Interface 声明文件:
virtual dtor
Factory Method (non-virtual func)
pvf
————————————————————————————————————————————————————————————————
3 Interface.cpp: Interface 实现文件
只含 dtor
————————————————————————————————————————————————————————————————
4 Imp.h: Imp 声明 (头) 文件
放 实现细节( 数据 )
————————————————————————————————————————————————————————————————
5 Imp.cpp: Imp 实现 源文件
————————————————————————————————————————————————————————————————
6 factory.cpp: Factory function 实现文件 :
调 真正 ctor: new Derived
return
std::shared_ptr<Interface>(
new Imp(name, birthday) );
————————————————————————————————————————————————————————————————
3.3 `Handle class 比较 Interface class`
(1) 利 / 弊 / 替代为 concrete class / inline
1) 利
`解耦 interface 与 implement`
=> 降低文件间 编译依赖`
2) 弊
———————————————————————————————————————
[1] `运行期 速度损失`: 间接 访问/调用
[2] `每个对象 多付 若干内存`
———————————————————————————————————————
3) 当 弊 > 利
=> 用 concrete class 替换 两者
4) 一旦脱离 inline 均 无法有 太大作为
(2) Handle class
1) `间接访问`
代价
`间接层: 必须通过 pImpl ptr`
2) `对象 内存` 增加
`pImpl ptr`
3) pImpl ptr 必须 在 Handle class 内 初始化
指向 `动态分配的 impl object`
动态内存`分配 / 释放` 的 `额外开销`
`bad_alloc` 的 可能性
(3) Interface class
1) 调 vf
代价: `间接跳跃`
2) derived 对象 内存增加
vptr
=== 详细
1 尽可能 postpone/延后 object definition
std::string encryptPassword(const std::string& password)
{
std::string encrypted; // 定义过早
if( password.length() < MinPwLen )
std::throw std::logic_error("password is too short");
... // 真正加密动作, 比如 将 加密后的密码放 encrypted 内
return encrtpted;
}
|
| 1 `延后 对象 定义, until 必须使用 对象 前一刻`
|/
std::string encryptPassword(const std::string& password)
{
if( password.length() < MinPwLen )
std::throw std::logic_error("password is too short");
std::stringencrypted;//定义过早
... // 真正加密动作
return encrtpted;
}
|
| 2 `真正动作 提取到 函数`
|/
void encrypt(std::string& s);
std::string encryptPassword(conststd::string& password)
{
...
std::string encrypted; // 用 default ctor 定义并初始化
encrypt = password; // assignment
encrypt(encrypted);
return encrtpted;
}
|
| 3 `构造时 初始化 效率高` 于 `default 构造 + 赋值`
|/
std::string encryptPassword(conststd::string& password)
{
...
std::string encrypted(password); // 用 copy cotr 定义并初始化
encrypt(encrypted);
return encrtpted;
}
4 `对象 只在 循环内 使用, 定义于 循环内还是外 更好?`
// 定义于 循环 外
Widget w;
for (int i = 0; i < n; ++i )
{
w = 取决于 i 的 某值;
}
// 定义于 循环 内
for (int i = 0; i < n; ++i )
{
Widget w(取决于 i 的 某值);
}
成本:
外: 1 default ctor + 1 dtor + n assignment
内: n ctor + n dtor
=> 若 1 assignment 成本 < 1 ctor + dtor 成本,
则 内做法 好
可通过 test 两种实现的 运行时间 来比较
2 少 转型
1 类型转换之 `新/旧 style
2 `唯一需 旧式转型 的 时机: 单 para explicit ctor`
class Widget
{
public:
explicit Widget(int size)
};
void f(const Widget& w);
f( Widget(5) ); // 旧式风格
f(static_cast<Widget>(5) ); // C++ 风格
3 `显式转型 未必需要`
int x, y;
double d = static_cast<double>(x) / y;
|
|
|/
double d = x;
d = d / y;
4 类型转换 往往令 `compiler 编译出 运行期 执行的 code`
`对象 ( Derived )` 可能拥有 `> 1个 地址`
以 Base*/Derived* 指向
offset 在 运行期 被施行于 Derived* 身上, 以 取得 正确的 Base*
=> `不要假设 对象 在 C++ 中 如何布局`
`除非 从 某个 compiler 说明文档里 明确得知`
Derived d;
Base* pb = &d; // Derived* 隐式转换为 Base*
5 对象 `转型结果` 是 `被转 part` 的 `临时 copy`
static_cast<Base>(*this).vf
对 *this 对象之 `base part 的 临时 copy 调 vf`
`base part 没被改, derived part 被改`
=> `当前对象` 成 `伤残` 状态
class B
{
public:
virtual void vf() { /* ... */ }
};
class D: public B
{
public:
virtual void vf()
{
static_cast<B>(*this).vf();
// process D part
}
};
|
| Base class name 限定
|/
class D: public B
{
public:
virtual void vf()
{
B::vf(); // (*this).B::vf();
// process D part
}
};
6 `dynamic_cast 之 低效 / 可能需要的场景 / 可能的 替代方案 / 必须避免 的 场景`
(1) 低效
至少有1个 实现 基于 `class name 之 字符串比较`
=> n 层单继承体系
可能需 n 次 strcmp 调用: 比较 class name
(2) 可能需要的场景
————————————————————————————————————————————————————————————————————————
[1] `多重继承 之 恢复接口`
[2] `用 non-virtual function + base class ptr/ref 处理 继承体系 中 obj`
————————————————————————————————————————————————————————————————————————
class B { ... };
class D: public B
{
public:
void f();
};
typedef
std::vector<std::shared_ptr<B> > Vspb;
Vspb vspb;
// ...
for(Vspb::iterator = vspb.begin(), iter != vspb.end(), ++iter)
{
if(D* pd = dynamic_cast<D*>(iter->get() ) )
pd->f();
// ...
}
(3) 可能的 替代方案
[1] 用 vf + base class 中 vf 用 缺省实现 (如 do nothing)`
+ 各 Derived class 的 vf override
[2] 用 多个容器, 每个 放 一种 派生类 SP
// [1]
class B
{
public:
virtual void vf() { } // 缺省实现: 啥也不干
};
class D: public B
{
public:
virtual void vf() { /* ... */ }
};
typedef
std::vector<std::shared_ptr<B> > VSPB;
VSPB vspb;
// ...
for(VSPB::iterator = vspb.begin(), iter != vspb.end(), ++iter)
(*iter)->vf();
(4) 必须避免 的 场景: 连串/if-else dynamic_cast
继承体系 变, client code 就要变
typedef
std::vector<std::shared_ptr<B> > Vspb;
Vspb vspb;
// ...
for(Vspb::iterator = vspb.begin(),
iter != vspb.end(), ++iter)
{
if(D* pd =
dynamic_cast<D*>(iter->get() ) )
// ...
else if(D2* pd =
dynamic_cast<D2*>(iter->get()))
// ...
// ...
}
3 避免返回 handles to object internals
1 3 大 风险
[1] `封装性` 降低 + [2]
class Point
{
public:
Point(int x, int y);
void setX(int x_);
};
struct RectData
{
Point ulhc; // upper left-hand corner
Point lrhc;
};
class Rectangle
{
private:
std::shared_ptr<Rectangle> pData;
public:
Point& upperLeft() const { return pData->ulhc; }
};
Point p1(0,0);
Point p2(100, 100);
const Rectangle rec(p1, p2);
rec.upperLeft().setX(50);
[2] `蓄意` + `有限度` 地 `放松封装性`
class Rectangle
{
public:
const Point& upperLeft() const { return pData->ulhc; }
};
[3] `handle dangling`
2 有时必须 让 mem func 返回 internal handle
STL 容器: string / vector
下标运算符 operator[] 返回 ref指向 `容器内数据`
4 为 exception-safe 尽力
1 `抛出异常` 时 `3 种 保证`
(1) `基本承诺`
不败坏数据 + `程序状态 任意(半构造状态)`
(2) `强烈保证`
`程序状态 不变`
`失败 时 回滚` 到 之前状态
(3) `nothrow 保证: 承诺 绝不抛出异常
内置类型 上操作
空白异常明细 empty exception specification // int f() throw();
不是指 不抛出异常
而是指 `抛出异常 时, 会有 意想不到的结果`
2 `异常安全 2 个条件`
[1] `不泄漏资源`
[2] `不败坏数据`
class PMenu
{
public:
void f(std::istream& img);
private:
Mutex mutex;
Image* p;
int count;
};
void PMenu::f(std::istream& img);
{
lock(&mutex);
delete p;
++count;
p = new Image(img);
unlock(&mutex);
}
1) 泄漏资源
new 抛出异常 + `unlock 被跳过`
=> mutex 永远被锁住
2) 败坏数据
`delete p + count 递增 + new 抛出异常
=> p 指向已删除对象
3 解决 `资源泄漏`
`资源管理 class ( RAII 类 ) Lock: 确保 mutex 被及时释放`
void PMenu::f(std::istream& img);
{
Lock lk(&mutex);
delete p;
++count;
p = new Image(img);
}
4 解决 `数据败坏`
[1] `raw pointer -> shared_ptr ( 其实也属于 资源管理: 避免 delete 被跳过 )
[2] 重排语句次序: 操作 ( new ) 后再 ++ `
class PMenu
{
public:
void f(std::istream& img);
private:
Mutex mutex;
std::shared_ptr<Image> sp;
int count;
};
void PMenu::f(std::istream& img);
{
Lock lk(&mutex);
sp.reset(new Image(img) );
++count;
}
5 `强烈保证: copy and swap`
`copy 欲 修改对象` -> `修改 副本` -> 在 不抛出异常的 `swap 中 置换 原件 和 副本`
+ `pImpl 手法`
[1] 将 `属于对象 的 data` 从 `源对象 放到 另一对象`
[2] 用 struct 而不是 class: 封装性 已由 spImpl 是 private 而获得
struct Impl
{
std::shared_ptr<Image> sp;
int count;
};
class PMenu
{
public:
void f(std::istream& img);
private:
Mutex mutex;
std::shared_ptr<Impl> spImpl;
};
void PMenu::f(std::istream& img);
{
using std::swap;
Lock lk(&mutex);
// (1) copy
std::shared_ptr<Impl>
spImplCpy( new Impl(*spImpl) );
// (2) modify copy
spImplCpy->sp.reset( new Image(img) );
++spImplCpy->count;
// (3) swap(orignal, copy)
swap(spImpl, spImplCpy);
}
5 深入理解 inlining
`inlining: 编译期` 将 `函数调用` 替换为 `函数本体`
1 inline 函数 `优缺点`
(1) 优
[1] 比 `宏` 好
[2] 无 函数调用 之 `额外开销`
[3] 让 compiler 有 `编译优化` 机会
(2) 缺
[1] `代码膨胀`
函数本体 比 函数调用 的 compiler 产出码 多
[2] `换页 paging -> 效率损失`
inline 过多
2 `inline vs. template`
(1) (通常) 均放 `头文件`
原因
`编译期行为`
compiler 编译期 `必须知道 inline 函数 / template 长什么样子`
`(2) template 实例化 与 inlining 无关`
若想 `template 所有可能 实例化 版本
都 inline`, 才 将 template 声明为 inline
// inline template 都放 头文件的例子
class Interface
{
public:
int age() const { return age; }
private:
int age;
};
template<typename T>
inline const T& std::max(const T& a, const T& b)
{
return a < b ? b : a;
}
(3) inline 只是申请, compiler 可 拒绝
3 `compiler 拒绝 inline 的 case`
[1] 函数太复杂
[2] vf
virtual: 等到 运行期 才确定 调用哪个函数
inline : 执行前 先将 函数调用 替换为 函数本体
两者矛盾
[3] `函数指针` 之 函数调用
[4] `ctor / dtor`
class Base
{
private:
std::string b1;
};
class Derived: public base
{
public:
Derived() {} // Derived ctor 并不是空的
private:
std::string d1, d2;
};
`compiler 真正造出来的 code` 可能为
Derived::Derived
{
Base::Base();
try { d1.std::string::string(); }
catch(...)
{
Base::~Base();
throw; // 销毁 base part 并 传播异常
}
try { d2.std::string::string(); }
catch(...)
{
d1.std::string::~string();
Base::~Base();
throw;
}
}
4 何时不该 inline
(1) `升级版 函数` 不该 inline
inline 函数 改变 -> 其 client code 必须重新编译
non-inline 改变 -> 其 client code 只需 重新连接
动态链接: 更不知不觉
(2) `debug 版 code` 禁止 inline
6 最小化 文件 间 编译依赖
含 `实现细节` 不含 `实现细节 ( 数据 )`
|\ |\
| |
| |
`定义式` 必须 `直接` 或 `间接` 列出 实现细节`
|\
|
|
1 `class 定义文件` 与 `#include 文件` 的 `编译依赖`
|
| 若 含
|/
[1] class 定义 的 实现细节
=> 实现细节 变, 则 class 的 client 要变
| |
| 如 | class 定义 文件
|/ |/
成员 Date Interface(类)
[2] #include 间 可能 `连串 依赖`
[3] #include 标准库 头文件 不太可能成 编译瓶颈
(1) `class 定义式` 含 `实现细节`
=> `修改 class 的 implement` 时, class 的 client 就` 需要 `重新编译`
#include <string>
#include "date.h"
class Interface
{
public:
Interface(const std::string& name,
const Date& birthday);
std::string name() const;
std::string birthDate() const;
private:
std::string _name; // 实现细节
Date _birthday; // 实现细节
};
(2) `class 定义式` 不含 `实现细节`
`修改 class 的 interface 时, class 的 client 才` 需要 `重新编译`
`pImpl` 手法 实现 真正的 `接口 与 实现 分离`
#include <string> // 标准库组件 不该被前置声明
#include <memory> // std::shared_ptr
class InterfaceImpl; // 本 class 的 实现类 前置声明
class Date; // 本 class 的 interface 用到的 class 前置声明
class Interface
{
public:
Interface(const std::string& name,
const Date& birthday);
std::string name() const;
std::string birthDate() const;
private:
std::shared_ptr<InterfaceImpl> spImpl; // pImpl
};
2 `最小化 编译依赖`
本质
`声明 的 依赖性` 替换 `定义 的 依赖性`
头文件 应 尽可能 `自我满足`, 万一做不到,
则 尽量依赖 other 文件内 声明式 (而非 定义式)
方法
[1] `用 ref/ptr` 能完成 任务, 就不要用 object
[2] `尽量用 class 声明式`, 除非 `真正需要 class 定义式`
`by value` 方式 `传参 或 返回值` 并 `不需要 class 定义式`
原因: `client 调用 function 前, 必先曝光( 实现出 ) class 定义式`
class Date;
Date today();
void clearAppointment(Date d);
[3] `为 声明式 和 定义式 提供不同的 头文件`
// 1 date.h 定义式 头文件
class Date
{
private:
int day;
int month;
};
// 2 dateDecl.h 声明式 头文件
class Date;
// 3 Date 的 client.cpp
#include "dateDecl.h"
Date today();
void clearAppointment(Date d);
3 2 种 Handle class
3.1 `Handle class & HandleImpl class`
将其 所有函数 `转交` 给相应 `实现类 的 相应函数`
两者 成员函数 接口完全相同
// 3 Interface.cpp: Interface 实现文件
#include "Interface.h" // #include Interface 类定义式
#include "InterfaceImpl.h" // #include InterfaceImpl 类定义式, 否则 无法调用其 成员函数
Interface::Interface(const std::string& name,
const Data& birthday)
: spImpl( new InterfaceImpl(name, birthday) ) {}
std::string
Interface::name() const { return spImpl->name(); }
3.2 `Interface class 即 abstract base class `
(1) 无 成员变量 + ctor
只有 1个 virtual dtor + 一组 pure virtual 函数,
描述 derived class 的 接口
Note
C++ Interface class / abstract base class 与 Java 中 Interface 区别:
C++ Interface class 中
`允许实现 成员变量 和 成员函数
|
|
|/
non-virtual function
|
| 如
|/
Factory Method: static mem func
(2) `client 必须 以 Interface class 的 ptr / ref 写程序`
原因: `无法` 对 `内含 pure vf` 的 Interface class `构造 新对象`
(3) `client 必须` 能为 相应 `derived class 创建对象`
`Factory Method`, 扮演 `真正将被 实例化` 的 `derived class` 的 `ctor` 的角色
|
|
|/
[1] 也叫 virtual ctor
[2] 通常是 static mem func
`支持 Interface class 接口 的`
`Implement class 必须被定义 出来` + `真正的 ctor 必须被调用`
|
| 这一切都在 `Factory Method 实现文件` 中 发生
|
|/
Interface 的 Derived 类
(4) 源文件结构
————————————————————————————————————————————————————————————————
1 client.cpp: 创建 对象, 支持 Interface 接口
————————————————————————————————————————————————————————————————
2 Interface.h: Interface 声明文件:
virtual dtor
Factory Method (non-virtual func)
pvf
————————————————————————————————————————————————————————————————
3 Interface.cpp: Interface 实现文件
只含 dtor
————————————————————————————————————————————————————————————————
4 Imp.h: Imp 声明 (头) 文件
放 实现细节( 数据 )
————————————————————————————————————————————————————————————————
5 Imp.cpp: Imp 实现 源文件
————————————————————————————————————————————————————————————————
6 factory.cpp: Factory function 实现文件 :
调 真正 ctor: new Derived
return
std::shared_ptr<Interface>(
new Imp(name, birthday) );
————————————————————————————————————————————————————————————————
// 1 client.cpp: 创建 对象, 支持 Interface 接口
#include <iostream>
#include <string>
#include "date.h"
#include "Interface.h"
int main()
{
// client 可能这样用
std::string name = "lilei";
Date date;
// 创建 对象, 支持 Interface 接口
std::shared_ptr<Interface>
spP(Interface::create(name, date) );
std::cout << spP->name() << "\n";
}
// 2 Interface.h: Interface 声明文件: virtual dtor + Factory Method (non-virtual func) + pvf
#ifndef Interface_H
#define Interface_H
#include <string>
#include <memory> //std::shared_ptr
#include "date.h"
class Interface
{
public:
// virtual Dtor 声明 Note: virtual Dtor 实现 放 Interface.cpp
virtual ~Interface();
// Factory Method 声明 Note: Factory Method 实现 factory.cpp
static std::shared_ptr<Interface>
create(const std::string& name, const Data& birthday);
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
};
#endif
// 3 Interface.cpp: Interface 实现文件, 只含 dtor
#include "Interface.h"
Interface::~Interface() {}
// 4 Imp.h: Imp 声明 (头) 文件: 放 实现细节( 数据 )
#ifndef Imp
#define Imp
#include "Interface.h"
#include <string>
#include "date.h"
class Imp: public Interface
{
public:
Imp(const std::string& name, const Date& birthday)
: _name(name), _birthday(birthday) {}
virtual ~Imp() {}
std::string name() const;
std::string birthDate() const;
private:
std::string _name; // 实现细节
Date _birthday; // 实现细节
};
#endif
// 5 Imp.cpp:Imp 实现 源文件
#include "Imp.h"
#include <string>
std::string Imp::name() const{ return _name; }
std::string Imp::birthDate() const { return " "; }
// 6 factory.cpp: Factory function 实现文件 : 调 真正 ctor
#include <memory> //std::shared_ptr
#include "Interface.h"
#include "Imp.h"
#include "date.h"
std::shared_ptr<Interface>
Interface::create(const std::string& name, const Data& birthday)
{
return
std::shared_ptr<Interface>(
new Imp(name, birthday) );
}
// 7 date.h
#ifndef DATE_H
#define DATE_H
class Date
{
private:
int day;
int month;
};
#endif
3.3 `Handle class 比较 Interface class`
(1) 利 / 弊 / 替代为 concrete class / inline
1) 利
`解耦 interface 与 implement`
=> 降低文件间 编译依赖`
2) 弊
———————————————————————————————————————
[1] `运行期 速度损失`: 间接 访问/调用
[2] `每个对象 多付 若干内存`
———————————————————————————————————————
3) 当 弊 > 利
=> 用 concrete class 替换 两者
4) 一旦脱离 inline 均 无法有 太大作为
(2) Handle class
1) `间接访问`
代价
`间接层: 必须通过 pImpl ptr`
2) `对象 内存` 增加
`pImpl ptr`
3) pImpl ptr 必须 在 Handle class 内 初始化
指向 `动态分配的 impl object`
动态内存`分配 / 释放` 的 `额外开销`
`bad_alloc` 的 可能性
(3) Interface class
1) 调 vf
代价: `间接跳跃`
2) derived 对象 内存增加
vptr
网友评论