你知道,当一个概念从一个专有名词变成一个普通名词时,说明它真正的深入人心了。比如Kleenex(面巾纸品牌,也指面巾纸),Xerox(施乐,复印机品牌,也指复印机)Q-Tips(化妆品品牌,也指化妆包),对吗?所以说,当我听说你可以在Visual C++.NET中使用“modern C++ design”时非常高兴也就不奇怪了。这里指的是——至少我这样认为——《Modern C++ Design》[1]所推行的基于模板的技术。
在所有泛型编程文章中都有一个成功的要素,就是进行特有至通用的“文法升华。”比如,在ScopeGuard[2]中增加了放入“撤消”动作于正常执行路径而当一个复杂操作成功时解除“撤消”的常见技术之后,ScopeGuard就成为了“域守卫(scope guard)”。我大多数受欢迎的文章不完全由我完成,而是和Petru Marginean紧密合作的结果。而且让我更加高兴的是再一次在本文中与他合作。
本文中我们将讨论对应于发布状态的机制:实施(enforcement),这是方便实用的快速条件验证机制。非常类似于ScopeGuard,ENFORCE宏极大程度地减少了你需要使用在错误处理上的代码。ScopeGuard和ENFORCE可相互独立工作,但最好是一起使用。ENFORCE是意外的源头,ScopeGuard是意外的传播者。
实施(Enforcements) 设想你注视着面前的一大堆代码。你知道你要在这些代码中杀出一条血路,你也知道你必须写更多的新代码。
一个好习惯是先浏览代码,试着找到一些通用模式。你要理解模式背后的概念。很有可能,所有这些都不是偶然发生的,而是同样的基本概念的不同表现形式,然后,你可以整理这些提取出的模式,这样你就可以扼要地表达所有的作为概念的实现体的模式
现在先暂停,看一下你要分析的不同模式的大小。如果这个模式很大,比如作为基础部分涉及到了整个程序,那么你正在和结构模式(architectural patterns)打交道。
如果模式是中等尺寸,横跨数个对象和/或函数,那么你遇到的是设计模式。
如果模式非常小,只包括3-10行代码,那么你面前的的常用法(idiom)。
最后,如果模式只有1-2行代码,你得到的东西叫做代码风格或格式。
这四种根据规模划分的类型所涵盖的范围类似于建筑学。在建筑学中,基本结构的小瑕疵不要紧。在软件架构中,任何缺陷都可能毁掉整个“建筑”。相反,你需要在任何范围使用正确的技术来确保成功。如果你对小细节着迷而忽略大的蓝图,你会浪费才华于建立不能远观的复杂的阿拉伯式建筑。如果你只注重大的方面而忽视细节,你会得到粗糙的庞然大物。
这是写软件异常困难的原因,这个困难是在其他领域工作的人们不能完全理解的。
实施属于常用法范畴。详细点说,实施极大简化了错误检测代码而不会影响可读性和正常工作流的流畅性。
想法来源于下列事实。你抛出一个意外非常可能是作为一个布尔值检测的结果,如下:
if (some test)
throw SomeException(arguments);
如果该意外在多处出现,为什么不把它放在一个小函数里呢:
template <class E, class A>
inline void Enforce(bool condition A arg)
{
if (!condition) throw E(arg);
}
可以这样使用它:
Widget* p = MakeWidget();
Enforce<std::runtime_error>(p != 0, “null pointer”);
Enforce<std::runtime_error>(cout != 0, “cout is in error”);
cout << p->ToString();
目前为止一切顺利.现在,我们来分析几个重要方面。
首先,被测条件不总是布尔型的,也可能是一个指针或整型。其次,非常可能你要在检测完之后马上使用被测值。比如,你可能希望在使用前确保一个指针非空,或你可能要在创建一个文件句柄后马上使用它。所以我们修改Enforce,这样它就有滤过机制来把接受的值传回:
template <class E, class A, class T>
inline T& Enforce(T& obj, A arg)
{
if (!obj) throw E(arg);
return obj;
}
template <class E, class A, class T>
inline const T& Enforce(const T& obj, A arg)
{
if (!obj) throw E(arg);
return obj;
}
(两个版本是必须的,分别对应const和非const对象。)你可以增加两个重载版本来表示你通常会抛出的那种意外和它所带参数。
Template <class T>
Inline T& Enforce(T& obj, const char* arg)
{
return this->Enforce<std::runtime_error, const char*, T>(obj,arg);
}
template <class T>
inline const T& Enforce(const T& obj, const char* arg)
{
return this->Enforce<std::runtime_error, const char*, T>(obj,arg);
}
如果你认为应该传入一个通用参数(信息)到std::runtime_error,调用可以进一步简化。你所需做的一些只是增加几个重载函数:
Template <class T>
Inline T& Enforce(T& obj)
{
return this->EnforceMstd::runtime_error, const char*, T>(obj,“Enforcement error”);
}
template <class T>
inline const T& Enforce(const T& obj)
{
return this->Enforce<std::runtime_error, const char*, T>(obj,“Enforcement error”);
}
现在,随着这些简单的扩展,代码变得相当得有表现力:
Enforce(cout) << Enforce(MakeWidget())->ToString();
在一行中你不单创建了一个widget对象并把它打印到控制台,你还标明在这个过程中可能会发生的任何错误!你也许还要自动释放创建的Widget对象,只需要在里面添加auto_ptr:
Enforce(cout) <<
Enforce(auto_ptr(MakeWidget()))->ToString();