Generic<Programming>:类型和值之间的映射[1]

[入库:2005年8月18日] [更新:2007年3月24日]

本文简介:选择自 hemingway 的 blog

generic<programming>:类型和值之间的映射
andrei alexandrescu

在c++中,术语“转化”(conversion)描述的是从另外一个类型的值(value)获取一个类型(type)的值的过程。可是有时候你会需要一种不同类型的转化:可能是在你有一个类型时需要获取一个值,或是其它的类似情形。在c++中做这样的转化是不寻常的,因为类型域和值域之间隔有有一堵很严格的界线。可是,在一些特定的场合,你需要跨越这两个边界,本栏就是要讨论该怎么做到这个跨越。

映射整数为类型

一个对许多的generic programming编程风格非常有帮助的暴简单的模板:

template <int v>
struct int2type
{
    enum { value = v };
};

对传递的每一个不同的常整型值,int2type“产生”一个不同的类型。这是因为不同的模板的实体(instantiation)是不同的类型,所以int2type<0>不同于int2type<1>等其它的类型的。此外,产生类型的值被“存放”在枚举(enum)的成员值里面。

不管在任何时候,只要你需要快速“类型化”(typify)一个整型常数时,你都可以使用int2type。比如这个例子,你要设计一个niftycontainer类模板。

template <typename t>
class niftycontainer
{
    ...
};

niftycontainer存储了指向t的指针。在niftycontainer的一些成员函数(member functions)中,你需要克隆类型 t的对象,如果t是一个非多态的类型,你可能会这样说:

t* psomeobj = ...;
t* pnewobj = new t(*psomeobj);

对于t是多态类型的情形,情况要更为复杂一些,那么我们假定你建立了这样的规则,所有的使用于niftycontainer 的多态类型必须定义一个clone虚拟函数(virtual function)。那么你就可以像这样来克隆对象:

t* pnewobj = psomeobj->clone();

因为你的容器(container)必须能够接受这两种类型,所以你必须实现两种克隆算法并在编译时刻选择适当的一个。那么不管通过niftycontainer的布尔(非类型,non-type)模板参数传递的类型是不是多态的,你都要和它交互,而且还要依赖程序员给它传递的是正确的标识。

template <typename t, bool ispolymorphicwithclone>
class niftycontainer
{
    ...
};

niftycontainer<widget, true> widgetbag;
niftycontainer<double, false> numberbag;

如果你存储在niftycontainer里的类型不是多态的,那么你就可以对niftycontainer的许多成员函数进行优化处理,因为可以借助于常量的对象大小(constant object size)和值语义(value semantics)。在所有的这些成员函数中,你需要选择一个算法,或是另外一个依赖于模板参数ispolymorphic的算法。

乍一看,似乎只用一个if语句就可以了。

template <typename t, bool ispolymorphic>
class niftycontainer
{
    ...
    void dosomething(t* pobj)
    {
        if (ispolymorphic)
        {
            ... polymorphic algorithm ...
        }
        else
        {
            ... non-polymorphic algorithm ...
        }
    }
};

问题是编译器是不会让你摆脱这些代码的。例如,如果多态算法使用了pobj->clone,那么niftycontainer::dosomething就不会那些任何一个没有定义clone成员函数的类型而编译。的确,看起来在编译时刻要执行哪一个if语句分支是很明显的,但是这不关编译器的事,编译器仍然坚持不懈地尽心尽职地编译这两个分支,即使优化器最终会消除这些废弃代码(dead code)。如果你试图调用niftycontainer<int, false>的dosomething函数的话,编译器就会停留在pobj->clone的调用之处,这是怎么回事?

等等,问题还多着呢。如果t是一个多态类型,那么代码将又一次不能通过编译了。如果t将它的copy constructor设为private和protected,禁止外部对其访问——作为一个行为良好的多态类,应该如此。那么,如果非多态的代码分支要做new t(*pobj),则代码不能编译通过。

如果编译器不为编译废弃代码费神那多好啊,但无望的期望不是解决之道,那么怎样才是一个满意的解决方案呢?

已经证实,有许多的解决办法。int2type就提供了一个非常精巧的解决方案。对应于ispolymorphic的值为true和false,int2type可以将特定的布尔值ispolymorphic转化为两个不同的类型。那么你就可以通过简单的重载(overloading)来使用int2type<ispolymorphic>了,搞定!

“整型类型化”(integral typifying)风格的原型(incarnation)如下所示:

template <typename t, bool ispolymorphic>
class niftycontainer
{
private:
    void dosomething(t* pobj, int2type<true>)
    {
        ... polymorphic algorithm ...
    }
    void dosomething(t* pobj, int2type<false>)
    {
        ... non-polymorphic algorithm ...
    }
public:
    void dosomething(t* pobj)
    {
        dosomething(pobj, int2type<ispolymorphic>());
    }
};

这个代码简单扼要,dosomething调用重载了的私有成员函数,根据ispolymorphic的值,两个私有重载函数之一被调用,从而完成了分支。这里,类型int2type<ispolymorphic>的虚拟临时变量没有被用到,它只是为传递类型信息之用。

不要太快了,天行者!

看到上面的方法,你可能认为还有更为巧妙的解决之道,可以使用比如template specialization这样的技巧。为什么必须用虚拟的临时变量,一定还有更好的方式。但是,令人惊奇的是,在简单性、通用性和效率上,int2type是很难打败的。

一个可能的尝试是,根据任意的t及ispolymorphic的两个可能的值,对niftycontainer::dosomething作特殊处理。这不就是partial template specialization的拿手戏吗?

template <typename t>
void niftycontainer<t, true>::dosomething(t* pobj)
{
    ... polymorphic algorithm ...
}

template <typename t>
void niftycontainer<t, false>::dosomething(t* pobj)
{
    ... non-polymorphic algorithm ...
}

看上去很美,可是啊呀,不好,它是不合法的。没有这样的对一个类模板的成员函数进行partial specialization的方式,你可以对整个niftycontainer作partial specialization:

template <typename t>
class niftycontainer<t, false>
{
    ... non-polymorphic niftycontainer ...
};

你也可以对整个dosomething作specialization:

template <>

本文关键:Generic Programming
 

本站最佳浏览方式为 分辨率 1024x768 IE 6.0(或更高版本的 IE浏览器)

go top