目录:
trick:Hands-On Design Patterns With C++(零)前言zhuanlan.zhihu.com本文提要:本文介绍两个方面:(1)对公共接口的控制,即根据不同条件打开或关闭公共接口。(2)重新绑定策略,即如果更改主模板类型,如何连相关的策略类型一并更新。
基于策略设计的高级技巧
使用策略控制公共接口(public interface)
前面我们已经使用过一种策略控制公共接口的方式。我们通过从策略继承来注入公共成员函数(参考第二篇WithRelease
增加公共接口)。这种方法相当灵活且功能强大,但是有两个缺点:
- 一旦我们从策略类公有继承,我们就无法控制注入的接口,策略的每个公共函数都是派生类就口的一部分!
- 如果要使接口生效,我们不得不将策略类强转为派生类,它还必须有权访问所有数据成员及派生类继承的其他策略。
现在,我们学习一种更直接的方法来操纵基于策略的类公共接口。 首先,让我们区分两个概念:有条件地禁用现有成员函数
和添加新成员函数
。
- 前者(有条件地禁用现有成员函数)是合理且安全的:如果特定实现不支持接口提供的操作,则就不应该提供此接口。
- 后者(添加新成员函数)是危险的:它使类公共接口的扩展失控。
所以,我们应该为策略类提供必要的接口,并禁用无关的接口。
C++有方法有选择地启用和禁用成员函数,我们经常通过std::enable_if
实现,其基础是我们在第7章讲到的SFINAE与重载解析管理
(Substitution Failure Is Not An Error 替代失败不是错误)。
为了说明使用SFINAE实现策略可以有选择地启用/禁用成员函数,我们通过有选择地禁用智能指针类中的operator->()
来进行示范。
首先,让我们回顾一下std::enable_if
如何启用或禁用特定成员函数:如果表达式std::enable_if <value, type>
结果为true,则将编译并产生指定的类型;如果值为false,则类型替换失败(不生成任何类型结果)。 此模板元函数的正确用法是在SFINAE上下文中,其中类型替换失败不会导致编译错误,而只是禁用失败的函数(更确切地说,它将其从重载解决方案集中删除) 。
由于使用SFINAE
启用或禁用成员函数所需的全部是编译时常量,因此可以使用constexpr值定义其他策略:
struct WithArrow {
static constexpr bool have_arrow = true;
};
struct WithoutArrow {
static constexpr bool have_arrow = false;
};
现在,我们可以使用std::enable_if
配合策略来控制是否产生operator->()
公共接口,如果为true,返回T*
,否则类型推倒失败:
// 使用允许->操作符策略
// 这种写法如果将ArrowPolicy设置为WithoutArrow,即使不使用operator->(),也无法通过编译!
template <typename T, typename DeletionPolicy = DeleteByOperator<T>, typename ArrowPolicy = WithArrow>
class SmartPtr : private DeletionPolicy {
public:
// 使用enable_if决定是否允许operate->(),如