Traits: 类型的else-if-then机制
Andrei Alexandrescu
Andrei Alexandrescu在位于华盛顿州西雅图市的RealNetworks公司中任开发经理。
什么是traits,为什么人们把它认为是C++ Generic Programming的重要技术?
简短截说,traits如此重要,是因为此项技术允许系统在编译时根据类型作一些决断,
就好像在运行时根据值来作出决断一样。更进一步,此技术遵循“另增一个间接层”
的谚语,解决了不少软件工程问题,traits使您能根据其产生的背景(context)
来作出抉择。这样最终的代码就变得清晰易读,容易维护。如果你正确运用了traits
技术,你就能在不付出任何性能和安全代价的同时得到这些好处,或者能够契合其他
解决方案上的需求。
例子:Traits不仅是泛型程序设计的核心工具,而且我希望以下的例子能够使你相信,
在非常特定的问题中,它也是很有用的。
假设你现在正在编写一个关系数据库应用程序。可能您一开始用数据库供应商提供的
API库来进行反问数据库的操作。但是理所当然的,不久之后你会感到不得不写一些
包装函数来组织那些原始的API,一方面是为了简洁,另一方面也可以更好地适应
你手上的任务。这就是生活的乐趣所在,不是吗?
一个典型的API是这样的:提供一个基本的方法用来把游标(cursor, 一个行集和或
者查询结果)处的原始数据传送到内存中。现在我们来写一个高级的函数,用来把某
一列的值取出来,同时避免暴露底层的细节。这个函数可能会是这个样子:
(假想的DB API用db或DB开头)
// Example 1: Wrapping a raw cursor int fetch
// operation.
// Fetch an integer from the
// cursor "cr"
// at column "col"
// in the value "val"
void FetchIntField(db_cursor& cr,
unsigned int col, int& val)
{
// Verify type match
if (cr.column_type[col] != DB_INTEGER)
throw std::runtime_error(
"Column type mismatch");
// Do the fetch
db_integer temp;
if (!db_access_column(&cr, col))
throw std::runtime_error(
"Cannot transfer data");
memcpy(&temp, cr.column_data[col],
sizeof(temp));
// Required by the DB API for cleanup
db_release_column(&cr, col);
// Convert from the database native type to int
val = static_cast<int>(temp);
}
这种接口函数我们所有人都可能不得不在某个时候写上一遍,它不好对付但又非常重
要,处理了大量细节,而且这还只是一个简单的例子。FetchIntField抽象,提供了
高一层次的功能,它能够从游标处取得一个整数,不必再担心那些纷繁的细节。
既然这个函数如此有用,我们当然希望尽可能重用它。但是怎么做?一个很重要的泛化步骤就是让这个函数能够处理int之外的类型。为了做到这一点,我们得仔细考虑代码中跟int类型相关的部分。但首先,DB_INTEGER和db_integer是什么意思,它们是打哪儿来的?是这样,关系数据库供应商通常随API提供一些type-mapping helpers,为其所支持的每种类型和简单的结构定义一个符号常量或者typedef,把数据库类型对应到C/C++类型上。
下面是一段假想的数据库API头文件:
#define DB_INTEGER 1
#define DB_STRING 2
#define DB_CURRENCY 3
...
typedef long int db_integer;
typedef char db_string[255];
typedef struct {
int integral_part;
unsigned char fractionary_part;
} db_currency;
...
我们试着来写一个FetchDoubleField函数,作为走向泛型化的第一步。此函数从游标处得到一个double值。数据库本身提供的类型映像(type mapping)是db_currency,但是我们希望能用double的形式来操作。FetchDoubleField看上去跟FetchIntField很相似,简直就是孪生兄弟。例2:
// Example 2: Wrapping a raw cursor double fetch operation.
//
void FetchDoubleField(db_cursor& cr, unsigned int col, double& val)
{