修饰Enforce
上面代码中所显示的“Enforcement failed”信息并不是很有用,所以我们需要对它做点修改。幸运的是,Petru的灵感一发而不可收拾。“那个脑袋从不停止工作!”
首先,包含在错误通知中的好的信息应该有不讨人喜欢的__FILE__和__LINE__。同时,能看到失败的表达式也会很有帮助。就象我们在Asserter[4]中所做的,我们建立一个小小的类来为我们保存这些信息:
template <class Ref> class Enforcer { Ref obj_; const char* const locus_; public: Enforcer(Ref obj, const char* locus) : obj_(obj), locus_(locus) {} Ref Enforce() { if (!obj_) throw std::runtime_error(locus_); return obj_; } }; |
obj_成员保存被检测的对象。Locus_成员是上述关于文件,行数,和表达式的信息。
为什么我们把Enforcer的模板参数叫做Ref而不是传统的T?原因是我们要总是用一个引用类型(不是一个值类型)来实例化Enforce,这会在接下去减少我们很多重复劳动。(如果你曾经写过类似的对const和非const引用的函数,你就会知道我的意思)
好吧,现在创建Enforcer对象,我们用一个小函数,这样我们可以轻松一点,让它来做类型推断:
template <typename T> inline Enforcer<const T&> MakeEnforcer(const T& obj, const char* locus) { return Enforcer<const T&>(obj, locus); }
template <typename T> inline Enforcer<T&> MakeEnforcer(const T& obj, const char* locus) { return Enforcer<T&>(obj, locus); }
|
我们现在只需要给蛋糕裱上奶油——意料之中的宏。
我们知道你讨厌宏,而且讨厌宏的不在少数,但我们更讨厌重复打__FILE__和__LINE__:
#define STRINGIZE(something) STRINGIZE_HELPER(something) #define STRINGIZE_HELPER(something) #something #define ENFORCE(exp) / MakeEnforcer((exp), "Expression '” #exp "' failed in '"/ __FILE__ "', line: "STRINGIZE(__LINE__)).Enforce() |
STRINGIZE和STRINGIZE_HELPER宏是预编译器必须的复杂过程来把__LINE__转换为数字。(不,#__LINE__没有用。)我从来都不完全知道这些宏怎样和为什么起作用(这和预编译器部分有关…啊,我脑海里开始涌现那悲惨的回忆!停下来,医生!)——而且,坦白说,我情愿去了解纽约城的下水道系统怎样运作也不想知道这里的细节。只要说STRINGIZE(__LINE__)产生了一个包含现在行数的字符串就足够了。那些这方面的专家[6]提供了完整的解释。
本专栏的一贯传统是不涉及编译器特性,所以我们只顺便提一下STRINGIZE技巧在MSVC的预编译器上会产生类似于(__LINE__VAR+7)的神秘字符串。
令人高兴的一面是,Enforcer的初始化代价只有两个指针赋值那样低廉,同时却保存了非常有用的信息。你可以方便地增加关于文件日期和编译时间的信息,以及非标准的信息,比如__FUNCTION__。
支持多个参数及ENFORCE的自定义判断条件
ENFORCE是个很好的想法,但如果你用了某样东西却发现在实际应用中却不如文章中宣称的那么有用,你不会感到受欺骗了吗?
我们会,并且我们已经发现ENFORCE中两个重要缺陷。
首先,通常更需要在默认的文件名,行数,和表达式信息的基础上增加——或代之以——传入一个自定义字符串的功能。
其次,ENFORCE只用!操作符来检测非零条件。然而,在真实应用中,有时候需要被检查的“错误”值不是零。许多使用整数返回值的API,包括在<io.h>中的标准C文件函数,返回-1来标识一个错误。另一些API使用一个符号常量。而COM使用更复杂的情况:如果返回值为零(就是S_OK),表示正常,如果返回值小于零,说明有一个错误,并且返回的实际值给出了错误的信息。如果返回值大于零,状态就是“带信息的成功”,就是说返回值中有一些有用的信息[5]。
显然我们需要一个更灵活的检测和报告框架。我们需要能够在两个层面上配置实施(判断条件和参数传入机制),最好在编译时配置,这样实施机制比同等的手写代码不会有更多的开销。(有一个明智的检查总是需要做一下:当某种抽象应用于具体情况,是否能比得上非抽象的解决方法?)
基于策略的(Policy-based)设计正适合于解决此问题。所以Enforce需要从一个简单的类改进为一个双策略参数的模板类。第一个策略是判断条件策略(处理检测事宜),第二个策略是抛出策略(处理构建和抛出意外对象)。
template<typename Ref, typename P, tyoename R> class Enforcer { …使用两个策略(看下一部分)… } |
两个策略都有非常简单的接口。以下是默认策略:
struct DefaultPredicate { template <class T> static bool Wrong(const T& obj) { return !obj; } } struct DefaultRaiser { template <class T> static void Throw(const T&, const std::string& message, const char* locus) { throw std::runtime_error (message + ‘/n’ + locus); } } |
 
2/2 首页 上一页 1 2 |