Stroustrup在《The C++ programming language》一书(第三版25.6.1节)中已提及用C++模板来实现带约束条件的类型。下面的代码示例了带范围约束条件的类型的实现,注意类型不占用额外的空间,并且在带相同约束的同类变量间及在兼容类型变量间传值时不需要作检验。
#include <iostream>
template <class C1, class C2> // 帮助在Check条件兼容时优化掉检测。
inline bool check_assignable(C1, C2) { return false; }
template <class T, class Check> class Checked {
T value;
public:
Checked(T v = 0) : value(v) { Check()(v); }
template<class C> Checked(Checked<T, C> other) { *this = other; }
Checked& operator=(T v) { Check()(v), value = v; return *this; }
template <class C> Checked& operator=(Checked<T, C> other) {
value = (T)other;
if (!check_assignable(Check(), C())) Check()(value); // 可能被优化掉
return *this;
}
operator T() const { return value; }
};
template <int begin, int end> struct Range {
struct error {};
template<class T> void operator()(T value) const {
std::cerr << "range checking: " << value << std::endl;
if (value < begin || value >= end) throw error();
}
};
template <int x0, int y0, int x1, int y1>
inline bool check_assignable(Range<x0, x1>, Range<y0, y1>) {
return x0 <= y0 && x1 >= y1;
}
int main () {
Checked<int, Range<0, 10> >
n1 = 5, // 检测
n2 = n1; // 不检测
Checked<int, Range<-5, 6> >
n3 = n1; // 检测
Checked<int, Range<0, 11> >
n4 = n1; // 不检测
Checked<double, Range<0, 10> > // OK,但Range只能是整数范围。
x = 3.5;
return 0;
}
说明:
1. 目前的C++语言(含C++11)仅支持将整型或者指针类型用于模板参数,因而下面的代码无法使用:
Checked<double, Range<-0.5, 0.5> > x = 0.1;
2. 字符串不能直接用于模板参数,而必须用下面的方法绕开:
template <const char* str> struct X {};
extern const char s[] = "hello";
X<s> x;
因而下面的代码不能直接使用:
Checked<std::string, Regex<"^\\d+$"> > digits = "0123";
3. 对象值(右值)不能用于模板参数的缺省值,否则代码可以更优雅地改写为:
template<class T, typename Check check> class Checked { /* ... */ };
Checked<int, Range(0, 10)> n = 5; // 将Range对象而不是类型用作模板参数
4. 代码注释中的“检测”、“不检测”,是目前的C++实现的执行结果。实际上,由于所赋的值在编译时已知,main()中的所有检测都能在编译时(通过函数内联)判定,因而充分优化后代码实际运行时不需要检测。
Checked<int, Range<0, 10> >
n1 = 5, // 检测
n2 = n1; // 不检测
Checked<int, Range<-5, 6> >
n3 = n1; // 检测
Checked<int, Range<0, 11> >
n4 = n1; // 不检测
讨论:
1. 在Java或.Net应用程序中,时常需要验证用户数据,而验证条件常用自定义的XML格式或字段标注表示(也可用标准的XML schema)。理想地,验证条件应当用变量类型自然表示。不过,如果要实现不占用额外空间的带约束类型,并且类型自动继承基类(如EmailString继承String),需要语言扩展。此外,变量可能需要额外的undefined取值,表示尚未被赋值或赋值失败的状态。2. C++有很多需要讨论的问题,例如如下的代码是非法的:
template<int x0, int y0, int x1, int y1>
inline bool _check_assignable<Range<x0, x1>, Range<y0, y1> >() {
return x0 <= y0 && x1 >= y1;
}
实际上,在面临实际问题时,考试已有的知识并不是目的。要探讨与改进C++语言,必须鼓励真诚的交流,而不是遮遮掩掩的藏私。但在学习阶段,我们就习惯于远不够清晰的文档与教材,把遮遮掩掩当成是考察方式,而不认为学生有理所应当的享受知识与不受捉弄的权力。实际工作中需要理解的知识已经足够多,即使有清晰的文档也并非轻易就能成为专家,更不用说作出贡献。对C++知识的最佳考察是有充足的将之用于实际应用编程的机会。一个团队的成绩完全取决于相互帮助的努力,我们远远没有作出应有的尝试与成绩。学习理应是享受,在学习时(如一个软件框架)不给予清晰的文档教材所养成的性格(基本的待人的态度)是愚蠢的。
另外,由众人在网上写技术博客是不明智的,我们需要专业的参考文档(免费或收费)。写博客不仅没有必要地泄露隐私,而且博客质量参差不齐难以成为参考。实际上,大家只需要将自己的补充意见发给专业的参考网站(或书籍、杂志、专业公司),而由网站列出贡献者即可。一门技术需要权威的、能找到答案的参考手册(可以有不同深度)及教程、示例,而不是搞不懂在网上胡乱翻查,后者也是没有必要的受捉弄与泄露隐私的行为。为了收集个人信息而设置文档障碍、或者针对个人筛选网页搜查结果是非法的。