Chinaunix首页 | 论坛 | 博客
  • 博客访问: 9487990
  • 博文数量: 1227
  • 博客积分: 10026
  • 博客等级: 上将
  • 技术积分: 20273
  • 用 户 组: 普通用户
  • 注册时间: 2008-01-16 12:40
文章分类

全部博文(1227)

文章存档

2010年(1)

2008年(1226)

我的朋友

分类: C/C++

2008-04-02 18:16:49

下载本文示例代码
摘要:实现某些约束需要太多的力气活,如果能找到某种通用方式实现那些更抽象的约束那就最好了,本文下面的解决方案将展示如何实现。



  通用的约束和算法常常给所处理的对象强加某些限制。例如,std::sort()算法需要其操作的对象元素定义 < 操作符。强制此约束很容易:编译器尝试针对给定类型调用该操作符。如果这个操作符不存在,你会得到一个编译错误:
#include 
struct S{}; //doesn't define operator <
int main()
{
    S s[2];
    std::sort(s, s+2); // 出错: 在类型 'S'中 'operator<' 没有实现
}

  但是,要表达约束以及强制执行约束并不是一件容易的事情。很多抽象的约束比如:“必须是一个基类”或“必须是一个 POD 类型”需要来自程序员更多的灵活性和技巧。本文下面将示范如何一通用的方式实现此类约束。

如何以通用的方式强制执行对象的编译时约束?

使用“约束模板”自动强制执行编译时约束

提出问题

  假设你的应用程序需要一个接口,这个接口是用 C 或者 SQL 编写的非C++模块。为此,你需要保证传递到非C++模块的所有对象具备 POD 类型。

struct S1 { int x;}; 
class S2 { public: void func(); }; 
union S3 { struct { int x, y; } t; char c[4];};
struct S4 : S1, S2 {}; 

以上数据都是 POD 类型,而下面这些则不然:

struct C1 { 
       virtual void func(); //有一个虚函数
}; 
struct C2 { 
       struct T{ int x, y; }; 
       ~C2(); //有一个析构函数
}; 
struct C3 : virtual S1 {} ; //有一个虚拟基类


  在个别编程实现中 POD 和 非 POD 的使用是有严格区分的,在 中定义的标准宏 offsetof() 就是一个例子。参见下列表达式:

size_t nbytes = offsetof (S, mem);

  该表达式以字节为单位返回成员 mem 的偏移量。按照 C++ 标准,S 必须是一个 POD 类(class),结构(struct)或者联合(union),否则,结果是不确定的。所以你的任务是编写一个约束,这个约束能在编译时自动区分 POD 和 非 POD 类型。一旦违反了“必须是一个 POD 类型”约束,编译器便会发出明确的出错信息。


实现约束

  约束实际上就是在某个类模板的成员函数中的一个表达式或者是一个声明。当约束被违反后,上述的表达式便触发一个编译错误。具有挑战的地方是要找到正确的编译时表达式,在约束被违反时激活这些表达式。此时熟悉 C++ 标准当然是有益而无害的。标准中说非 POD 对象不能是一个联合(union)的成员(见标准的 clause 9.5)。利用这个限制,创建一个,让其唯一成员就是你要测试的对象不就行了。

template  struct POD_test
{
 POD_test()
 {
  union 
  {
   T t; //T 必须是一个 POD 类型
  } u;
 }
};

  编译器只为实际被调用的成员函数产生代码,或者显式或者隐式。因此,在某个类模板的构造函数或吸构函数中实现该约束将保证其编译时能处理其每个实例。(稍后我们将看到如何改进此设计)
为了测试这段代码,你可以使用各种不同的模版参数来进行实例化:

//下列三条语句通过编译
POD_test  pi;
POD_test  ps1;
POD_test  ps4;

//编译失败
POD_test  pstr;
POD_test  pc1;
POD_test  pc2;

正像我们期望的那样,由于后面三个实例其模板参数不是 POD 对象,编译器在处理时发出了出错信息。

改进设计

  约束能导致运行时和空间上的开销吗?如果它在编译时检查,你肯定不想让它呆在可执行文件中,现代 IDEs 都很聪明, 将可执行文件中不必要的代码优化掉。为了让编译器报错,将约束移到一个单独的静态成员函数 constraints() 中(你也可以另外的函数名)。记住声明时时这个成员是 private,以便其它程序无法调用它:

template  struct POD_test
{
 POD_test(){constraints();} // 强制编译时处理
private:
 static void constraints()
 {
  union{ T t;} u;
 }
};

  注意 constraints() 函数实际上什么也没做,它只是声明了一个局部联合类型的变量。由于该联合变量没有被使用,编译器可以省略掉这个构造函数中的 constraints(),从而避免了必要的开销。
“必须是 POD ” 只是众多强制约束中一个案例。另一个常用的约束是“必须是 T”,实现这个约束也非常简单:

//检查 T1 是否为 T2
template  struct is_a_T2
{
 is_a_T2() {constraints();}
 static void constraints()
 {
  T1 t1;
  T2& ref=t1; // 如果 t1 不是 t2 则出错
 }
};
  这里是另外一个约束:“必须是一个整型”,这个也不难,有多种技术途径来实现。其中之一就是是使用该对象作为某个数组的下标。因为 C++ 需要整型作为下标,使用任何其它的类型都将导致编译错误。我将这个实现留给读者来做。
 
作者简介
  Danny Kalev 是一名通过认证的系统分析师,专攻 C++ 和形式语言理论的软件工程师。1997 年到 2000 年期间,他是 C++ 标准委员会成员。最近他以优异成绩完成了他在普通语言学研究方面的硕士论文。 业余时间他喜欢听古典音乐,阅读维多利亚时期的文学作品,研究 Hittite、Basque 和 Irish Gaelic 这样的自然语言。其它兴趣包括考古和地理。Danny 时常到一些 C++ 论坛并定期为不同的 C++ 网站和杂志撰写文章。他还在教育机构讲授程序设计语言和应用语言课程。
下载本文示例代码
阅读(679) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~