一个可识别联合(Discriminated Unions)的C++实现 Andrei Alexandrescu 概要: 可识别联合(也是常说的可变类型或标记联合)是一种存放对象和对象所属类型标记的数据结构,对象可来自于任意的类型集合。可识别联合在某些应用中象解释器,数据库程序和数据通讯中是非常有用的。一些用c++来实现的可识别联合已经出版[1],[2]。这篇文章介绍了基于c++泛型的可识别联合的实现,这个实现有下列特征:(1)指定可接受类型集合的能力。(2)对内建类型和用户定义类型无区别。(3)用户可使用“完全脱散”("total decoherence")模式获取存储在可识别联合中所有可能类型,否则给出编译错误。{4}避免使用自由存储区和提供O(I)效率的类型转换来获得高效率。特征{1}和{3}相对于其他实现尤为新颖。 这篇文章使用Loki C++类库[3]并假定读者也具有这方面知识。Loki为设计模式和常用手段(idiom)提供了泛型组件,这样用户就不必从很底层开始设计。用到的组件为typelists,Visitor和hierarchy generators,
1. 介绍 许多应用程序需要用统一的格式存储不相关的数据类型。假设有个和数据库通讯的应用 程序。数据库存储的字段是属于某种类型的,比如字符串,整数,小数,浮点数和日期。当查询数据库时,结果是以表的形式返回,表里包含这些类型的列。当写一个查询功能时,c++程序必须把这些类型用统一格式表示,这样才能统一存储这些查询结果(比如用二维数组) 假设 现在有个老的基于C的数据库API,它把字段(field)值存到能接受所有可能类型的 联合,这个联合还有一个整数或枚举值(标签(tag)),这个标签用来指定联合的哪个字段(field)当前有效,例如:
//示例1:用C风格的方式表示一个数据库的字段类型 struct DatabaseField { enum TypeTag { typeNull, typeInt, typeDouble, typeString, typeDate } tag_; union { int fieldInt_; double fieldDouble_; char* fieldString_; Date typeDate_; }; }; 不用说,这种方法既笨拙又易错。 面向对象的方法在面对前面描述的关系数据库时用多态(poliymorphic)的字段对象。这 个方法有类型安全(type safe)的优点。然而这个优点被笨拙的设计抵消了。面向对象编程(至少是象C++那样的静态类型)预先要定义一个类层次的统一接口,在我们现在的这个例子里这种设计是不现实的。因为象字符串、整型、日期型或布尔型这些不同的类型没有一个通用的接口。最终,这种方法最后结果只能是:大量的类型转换或是臃肿的易错的接口,在这些接口里都有对每个类型的运行时(runtime)检查 另外一个需要可识别联合的例子是,在动态类型语言(LISP,Basic)中,变量属于哪种类 型取决于它被赋与什么值。在取值时,这些语言根据上下文推断和检查变量类型。实现这些类型引起的问题和前面提到的数据库变成引起的问题相似。
2、类型安全的可识别联合 上面的示例1描述了一个C风格的可识别联合设计,必须用下面的步骤来消除那种设 计的弱点。 * 引入类型安全。带标记的联合没有提供类型安全。必须有一种不牺牲性能的类型安全机制。 * 通用化可能类型的集合。为了让可识别联合更加通用,用到的类型不能写死在联合里,而是能让用户在外部配置 * 象支持基本类型一样支持用户自定类型。在示例1中的fieldString_成员有个细节问题。通常,如果tag_等于String类型时,DatabaseField的析构函数对fieldString_调用delete[],这样的做法违反了通用化原则(译注:因为在析构成员类型时可能调用delete[],delete或什么都不用做,这完全取决当时联合内存储什么类型的对象。所以说违反了通用化原则)。最好的方法试是用设计良好的用户自定义类型储存string字段,象std::string。然而C++联合不能存储带有构造和析构函数的类型。可识别联合的通用实现方法必须无缝地同时支持内建和用户自定类型。 一个通用化可识别联合中支持类型的可能方法是用typelists。Typelists[按年份排序的参考资料是7、8、9、3]能操作类型串,有和值串相似的功能。这篇文章接下去都使用[3]定义的模板类Loki::Typelist。 一个待实现的variant类的骨架就象这样 template <class Tlist> class Variant { ... } 用户可以通过用Typelist对象实例化Variant来定义包含指定类型集合的Variant: //示例2:一个用typelist实现可识别联合的方法 typedef Variant< TYPELIST_4 (int, double, std::string, Date)> DatabaseField; Variant类型必须至少要包含以下特征 * 值语义(Value semantics);默认构造函数,拷贝构造函数,赋值操作函数(assignment operator),和不会泄漏资源的析构函数 * 有接受包含任意类型的typelist的构造函数 * 赋值操作函数能接受包含任意类型的typelist * 能使用类型安全的方法转化到任何包含在传入typelist的类型 * 存在一个函数能返回现在实际储存在Variant的类型 * 使用惯用法(idioms)和一些STL算法支持两个Variant对象间高效率的值互换(swapping) * 如果能够执行类型转化,有高效的方法改变Variant对象的类型。例如,int改变到double,这需要Variant的操作函数拥有灵活性和一致性
3、存储 实现灵活的类型安全的基于C++的可识别联合的典型的方法是多态加上"pimpl"惯用法。这样的设计可以获得前述的所有功能甚至更多,但是,是以使用不必要的自由存储区(free store)来分配内存。 这篇文章描述的实现会正确地存储variant对象中的任何类型。为了达到这个目的,Variant需要一个编译时算法能够计算出typelist中类型的最大尺寸。算法描述如下:
template <class Tlist> struct MaxSize;
template <class T> struct MaxSize<Loki::NullType> { enum { result = 0 }; };
template <class Head, class Tail> struct MaxSize<Loki::Typelist<Head, Tail> > { private: enum { tailResult = (size_t) (MaxSize<Tail>::result) }; public: enum { result = sizeof(Head) > tailResult ? sizeof (Head) : (size_t) (tailResult) }; }; 任意一个typelist Tlist,MaxSize<Tlist>::result在编译时返回所有包含在Tlist的类型中最大的尺寸。使用MaxSize,Variant按下面的方式存储数据(对齐(alignment)问题在下一节处理): template <class Tlist> class Variant { enmu { size = MaxSize<Tlist>::result }; unsigned char buffer_[size]; ... }; 还有,Variant 必须存储鉴别器(discriminator)。即,帮助正确分辨存储在原始缓存(raw buffer)中的类型的标志。一个简单的方法是用整型标志。由于灵活性和速度的原因,Variant 存储了指向静态函数指针数组的指针 —— 一个对虚函数表的模拟。第5节描述了类型标 志的实现
4、对齐计算 对于解决C++的对齐问题。没有哪种方法是完全通用的。因为这个语言缺乏适合的基本功能(primitivess)(如:某些编译器有_alignof_关键字扩展)。然而,对齐能正确地在很多平台上实现,只要付出些努力和做一些合理的假设。 可以这样解决对齐问题:给定一个typelist,返回一个POD(简单数据plain old data)类型,能够保证对typelist中任意类型的适当对齐。 为了计算对齐,我们首先考虑一个名为TypeofAllAlignments的typelist,它包含的类型有各种不同的对齐 a) 所有基本类型 b) 指向所有基本类型的指针 c) 函数指针 d) 成员变量指针 e) 成员函数指针 f) 带虚函数的类 针对在(a)到(e)提到的每个类型,增加一个POD结构,这个结构包含该类型的唯一成员。 增加结构是必须的,因为在一些编译器上对齐结构和对齐基本类型的不同的,即使当他们内部结构和基本类型一样。 AlignmentCalculator算法计算一个名为Tlist的typelist的对齐。计算步骤如下 * 把TypesofAllAlignments赋给Temp * 用第三节中介绍的MaxSize模板类计算在Tlist中所有类型的最大尺寸。把结果存放在编译时常量maxSize中。 * 把Temp中所有大小大于maxSize的类型去除。 结果是一个联合类型,它里面包含了Temp里剩余每个类型的成员。因为Temp里面只 包含POD类型,所以构造这个联合不成问题。(不能直接用TList来构造联合,因为Tlist里面可能有存在构造函数和析构函数的用户自定类型。) 一个得到恰当的对齐是的一般办法是,单单定义一个包含TypesofAllAlignments内每个 类型的联合。这个方法是以分配额外内存为代价来获得可能的最大对齐需要。AlignmentCalculator的有趣之处在于,不象那个简单的方法,AlignmentCalculator没有额外的内存开销和兼容性损失也能计算对齐。 有了AlignmentCalculator,Variant以如下方式实现对齐: template <class Tlist, typename Align = AlignmentCalculator<Tlist>::Result> class Variant { enum {size = MaxSize<Tlist>::result }; union { unsigned char buffer_[size]; Align dummy_; }; ... }; 对齐操作由模板参数指定,默认是计算好的值。这样能让有不同对齐需要的平台的Variant用户能够指定需要的对齐类型而不用改变Variant的代码。 上面定义的存储结构能够让Variant正确存放任意基本类型或用户自定义类型,避免了任何不必要的自由存储区分配。
5、类型标志 就象前面大致提到过的,Variant通过一个指向函数表的指针来对类型进行分辨和操作。这是个通用,高效的方法。表结构描述如下: template <...> class Variant { struct Vtable { const std::type_info& (*typeId_) (); void (*destroy_) (const Variant&); void (*clone_) (const Variant&, Variant&); void (*cloneTypeOnly_) (const Variant&, Variant&); void (*swap_) (void* lhs, void* rhs); bool (*changeType_[Loki::TL::Length<Tlist>::value]) (Variant&); ... }; Vtable* vptr_; ... }; 上面用到的标识名称暗示了所用的结构和所谓的"vtable"和"vptr"(用在C++虚函数的典型实现方式的行话)之间的相似性。这些函数作用描述如下: * typeId_指向的函数返回当前存放对象的std::type_info * destroy_在Variant对象析构时调用 * clone_指向的函数复制Variant对象 * cloneTypeOnly_指向的函数复制一个空的Variant对象(只复制类型,不复制值) * swap_交换两个Variant对象的内容 * changeType_是函数指针的固定大小数组,这些函数改变Variant对象的类型,使之成为实例化这个Variant的typelist内的任何其他类型 Vtable需要函数指针来初始化它的成员。这些都在VtableImpl模板类中提供,也定义在 Variant内 Template <...> Class Variant { template <class T> struct VtableImpl { static const std::type_info& TypeId() { return typeid(T); } static void Destroy(const Variant& var) { const T& data = reinterpret_cast<const T*>(&var.buffer_[0]); data.~T(); } ... static Vtable vtbl_; }; ... }; 对任何类型T,VtableImpl<T>都有静态成员函数对应所有Vtable的成员,还包含有一个静态Vtable成员变量,名为vTbl_。当初始化一个Variant对象时,它的vptr_初始指向合适的VtableImpl <T>::vtbl_,VtableImpl <T>::vTble_的实例化取决于用于初始化Variant的类型T。定义在Loki内的STATIC_CHECK宏,在编译时检查布尔常量,如果常量是false时给出编译时错误。 Template <...> Class Variant { ... public: template <class T> Variant(const T& val) { STATIC_CHECK((Loki::TL::Indexof(Tlist, T>::value >= 0), Invalid_Type_Used_As_Initializer); New(&buffer_[0]) T(val); Vptr_ = &VtableImpl<T>::vTbl_; } }; 在确定了传入的类型属于支持的类型集后,一个T对象构造在buffer_所属的空间内,vptr_成员设置为指向一个处理对象类型为T的VtableImpl实例; 因为buffer_和vptr_是一起初始化的,VtableImpl的函数能够安全地认为buffer_包含一个T对象并能通过reinterpret_cast得到它/ 一旦Vptr_正确初始化,Variant能通过存储在vTbl_内的函数指针执行所有的功能。 现在我们来看VtableImpl<T>::VTbl_的初始化,每个VtableImpl<T>必须传入T到Vtable的构造函数。这能够由Loki::Type2Type这个简单的模板类来实现,Type2Type能够携带类型信息而没有创建这个类型的值的开销。 Vtable的构造器由T来实例化(templated),接受一个没用的参数Loki::Type2Type<T>。然后对每个函数指针初始化,指针指向在VtableImpl内定义的函数地址: Template <...> Class Variant { ... struct Vtable { template <class T> Vtable(Loki::Type2Type<T> tt) { typeId_ = &VTableImpl<T>::TypeId; destroy_ = &VtableImpl<T>::Destroy; clone_ = &VtableImpl<T>::Clone; cloneTypeOnly_ = &VtableImpl<T>::CloneTypeOnly; swap_ = &VtableImpl<T>::Swap;
Init(changeType_, tt, Tlist()); //看下面 } ... }; ... };
6、转换 vTable一个值得特别关注的成员是: bool (*changeType_[Loki::TL::Length<Tlist>::value])(Variant&); changeType_的类型是函数指针数组(长度为Loki::TL::Length<Tlist>::value)接受Variant&参数,返回bool。这个数组用来改变一个Variant对象内的类型。这个数组的第N个函数的语法是:如果从现在类型到typelist的第N个类型存在转换,函数把Variant对象转换为那个第N个类型并返回true。否则,函数返回false。 Typelist支持的每个类型在数组中都有个位置。于是,从任意类型转换到任意其他类型花费O(1)的时间,O(1)取决于typelist存储的类型数目。 理想状态下,changeType_成员和静态Vtable对象的所有其他成员都应该会在编译时初始化。但这其实是不可能的,因为在C++中,静态数组初始化语法不能让它在编译时通过泛型代码扩充自己。 检查是否能转换通过使用Loki::Conversion模板类来实现。这个模板类在[3]中描述。类型转换通过在编译时选择一个返回false的没用的函数或一个执行转换并返回true的函数。 下面是初始化changeType_数组的代码 template <...> class Variant { ... struct Vtable { ... template <class T, class Tlist> void Init(bool (**pChangeType) (Variant&), Loki::Type2Type<T> tt, Tlist) { typedef typename Tlist::Head Head; typedef typename Tlist::Tail Tail; enum {canConvert = Loki::Conversion<head, T>::exists != 0 }; *pChangeType = &VtableImpl<T>:: Converter<Head, canConvert>::Convert; Init(pChangeType +1, tt, Tail()): } template <class T> void Init(bool (**)(Variant&, Loki::Type2Type<T>, Loki::NullType) { //nothing to dp - stop recursion } }; };
这个Init模板函数在编译时递归。每次调用Init初始化一个函数指针,然后执行一个尾部递归来初始化剩余指针。这个递归时通过Init的最后参数来实现,这个参数是个typelist,Init通过每次递归调用来缩小这个typelist(把头取出。译注:如果对这些递归操作不熟,强烈建议看[3]的typelist部分)。最后,接受一个Loki::NullType对象(typelist的终止符)的Init重载终止递归。 Converter是个简单的模板类,在二种情况下特化-类型转换存在或不存在。然后提供一个静态函数Convert来执行转换。
7、脱散(Decoherence)和参观(Visitablility) 在这篇文章中,“脱散”指寻找储存在Variant对象中的实际类型。这个术语是从量子理论体系借用而来。原本是指从违反常识的量子理论的cuantic(译注:抱歉无法找到这个单词的意思)体系切换到典型的物理学行为。相似的,对Variant对象来说,“脱散”意指寻找被Variant隐藏起来的“典型”的C++类型。 Variant的最简单的“脱散”通过模板函数GetPtr来执行。如果Variant存有类型int,Variant<Tlist>::GetPtr<int>()返回指向这个int对象的指针,否则返回0 Template<...> Class Variant { ... public: const std::type_info& typeId() const { return (vptr_->typeId_)(); } template <typename T> T* GetPtr() { return TypeId () == typeid(T) ? reinterpret_cast<T*>((&buffer_[0]) : 0; } }; 还有一个const版本的GetPtr。类似的为了方便还加了二个模板函数Get和added。它们返回引用而不是指针。如果要求的类型和存储在Variant内的类型不同,抛出std::runtime_error。另外,如果你在非const的Variant存储是T但想获得const T,结果是操作失败。这个行为在设计时确定。 如果提供了你所知的类型,使用GetPtr和Get可以对Variant对象存储的对象进行操作: typedef Variant< TYPELIST_4(int, double, std::string, Date)> DatabaeField; ... DatabaseField fld; ... if (int* pInt = fld.GetPtr<int>()) { ...pInt points to the integer stored... } else if (double* pDbl = fld.GetPtr<double>()) { ...pDbl points to the double stored... } else of (std::string* pStr = fld.GetPtr<std::string>()) { ...pStr points to the string stored... } else if (Date* pDate = fld.GetPtr<Date>()) { ...pData points to the Date stored... } else { assert(false); //不应该到这里 } 这种手动脱散的问题是:这种代码在面对变化时非常脆弱。如果用户以后再往Variant加类型,编译器在if/else语句数量爆炸的情况下无法确保每个类型都被检查(上面的assert就会被触发)。 一个更好的能由编译器检查的脱散机制是,对Variant使用Visitor模式[5]。在可识别联合和Visitor模式间有很强的关联。这是因为Visitor提供了对类型集执行不同的和不相关的操作的能力。每个操作转化为从一个抽象基类派生的实类,每个类型相关的操作单元转化为一个虚成员函数。操作流确保根据被访问的实际类型来调用正确的成员函数。 想象一下对Variant使用Visitor。对一个Variant实例, Visitor模式预先定义一个抽象类,这个抽象类有对应Variant可能包含的所有类型的Visit成员函数。例如,对于示例2的DatabaseField类型,必须要定义下面的接口: struct DatabaseFieldVisitor { virtual void Visit(int&) = 0; virtual void Visit(double&) = 0; virtua; void Visit(std::string&) = 0; virtual void Visit(Date&) = 0; }; 在DatabaseField内的每个类型都在DatabaseFieldVisitor中对应一个纯虚函数。这意味着对每个Variant实例,用户都必须定义一个专用的Visitor基类。然而这个过程可以自动实现,在[3]中有具体描述。当使用Loki的Visitor通用实现,上面的DatabaseFieldVisitor变为: typedef Loki::CyclicVisitor<void, TYPELIST_4(int, double, std::string, Date)> DatabaseFieldVisitor; Loki能通过typelists来定义通用Visitor界面,这样就能把上面的typedef添加到Variant内。这样客户代码就能使用它了: Template<...> Class Variant { ... public: typedef Loki::CyclicVisitor<void, Tlist> StrictVisitor; }; 为了使用Loki提供的Visitor模式,Variant必须定义Accept函数,这个函数接受唯一参数:StrictVisitor,.然后把它传给存储的类型。这通过在Vtable里增加一个新的函数指针,然后在Variant的Accept成员函数里调用它:
template <...> class Variant { ... public: typedef Loki::CyclicVisitor<void, Tlist> StrictVisitor; Void Accept(StringVisitor& visitor) { (vptr_->accept_)(*this, visitor); } }; accept_绑定的具体函数实现简单地调用正确的Visit的对于StrictVisitor对象的重载函数。 最终的良好结果是,写出正确的确定的处理所有Variant内包含的类型的程序。从Variant<..>::StrictVisitor继承的客户程序必须实现所有成员函数,否则就会得到编译时错误。 但是,一些用户可能会希望在不放弃访问(visiting)的好处情况下少一点访问过程的限制。 比如说,设想有一个数据库功能,它对查询结果的特定列执行复杂的数字计算。这个功能只对double型和DatabaseNull(一个指出在关系数据库中字段为空的占位符)类型。如果只为了满足类型要求,实现什么都不做的存根(stub)函数时非常糟糕的;在这种情况下,需要一个更复杂的方法。 在[6]中描述的Acyclic Visitor正是对这种需要的解决手段。Acyclic Visitor是个更灵活的方法,能允许Visitor对象访问所有给定类型的任意子集。Acyclic Visitor在[3]中也有介绍,同时提供了一个泛型实现。通过使用那个实现,我们可以方便地对Variant实现Acyclic Visitation:
template <...> class Variant { ... public: typedef Loki::BaseVisitor NonStrictVisitor; Bool Accept(NonStrictVisitor& visitor) { return (vptr_acceptNonStrict_)(*this, visitor); } }; 这两个访问方法可以并存且绝对是互不影响。如果类型确实被访问到了,这个Accept的非限制版本返回布尔值true。 Variant还增加了二个Accept重载函数:上面二个介绍的函数的const版本。没有这二个新增函数就不能访问Variant的constant对象。
8、Variant-to-Variant Conversions 一点实现了访问功能,一些原本复杂的工作也变得非常简单了。考虑下面的例子,在Variants之间互相转换: typedef Variant< TYPELIST_4(int, double, std::string, Date)> DatabaseField; typedef Variant< TYPELIST_3(unsigned int, unsigned short, std::string)> FilteredData; ... DatabaseField dbField; ... FilteredData myData(dbField); 我们的目的是用一个Variant的实例去初始化另一个Variant的实例。如果源Variant(在这里是DatabaseField)包含的一个类型能转换为目标Variant(在这里是FilteredData)的类型,那么这个Variant之间的转换是被允许的。 举例说,如果dbField包含一个std::string,myData应该能被这个string初始化。如果dbField包含的是一个Date,这个初始化动作就会抛出一个意外(假定Date不能转换为string或一个整型)。最后,如果dbField包含一个int,由于int能同时转换为unsigned int和unsigned short,一个二义的意外会被抛出。 综上所述,把一个Variant实例转换为另一个的构造器执行下列算法: * 如果源Variant存储对象的类型在目标Variant类型的typelist中,那么目标Variant存储的对象值也是源Variant存储对象的值,存储的实际类型也是同样。 * 如果源Variant类型能隐式转换为目标Variant的Typelist中的一个类型,那么就会执行这个转换 * 其他所有情况都会抛出意外 这个可能会很慢的算法能用访问(visitation)来优美高效地解决。目标Variant的构造 函数用一个“转换visitor”访问源Variant实例。在它的Visit成员函数中,ConvertingVisitor在编译时执行寻找和分派来初始化目标Variant,当有二义性或不能转换时会抛出异常。 Convertingisitor是用Loki::GenLinearHierarchy[3]来生成,这是一个类库的功能,能够生成完整的类层次。在这个情况中,需要类层次来执行所有的Visit重载函数。再一次的,我们用Loki::Conversion来决定类型是否能转换。 9、Variants支持自由类型(unbounded types) 我们来回顾Variant的模板构造函数 template <...> class Variant { ... public: template <class T> Variant(const T& val) { STATIC_CHECK((Loki::TL::Indexof<Tlist, T>::value >= 0), Invalid_Type_Used_As_Initializer); New(&buffer_[0]) T(val); Vptr_ = &VtableImpl<T>::VTbl_; } }; 在构造函数中用STATIC_CHECKT来保证T属于Variant能接受的类型集合。所以,Variant接受的所有类型都和buffer_及Align匹配。 这样的策略引入许多有趣的扩展应用。类型不再局限在定义好的集合中,你能在Variant中存储任意类型,只要它满足空间和对齐的限制。包括那些开始定义Variant没考虑到的类型。如果Variant的空间大到足够放指针或聪明指针,Variant就变成能存放任意类型的数据包。 为了使用自由类型,我们只要在Variant中另加一个模板参数类型:一个用来指明Variant包含确定类型或自由类型的标志。类定义和构造函数就变成: template <class Tlist, bool unBounded = false, class Align = AlignmentCalculator<Tlist>::Result> class Variant { ... public: template <class T> Variant (const T&val) { STATIC_CHECK( UnBounded && sizeof(T) <= sizeof(buffer_) || (Loki::TL::IndexOf<Tlist, T>::value >= 0), Invalid_Type_Used_As_Initializer); new(&buffer_[0]) T(val); Vptr_ = &VtableImpl<T>::VTbl_; } }; 这个方法结合了限定的和自由的可识别联合的长处:你还能舒舒服服地使用typelist的类型,但同时还能存储不在typelist中的其他类型, 如果你完全没有兴趣知道应该包含那些类型。但想存储空间开销小于某个固定值的对象,你能让Tlist只包括char[N]。例如,下面的typedef定义了一个Variant,它能存放的类型大小最多是64个字节。 Typedef Variant<TYPELIST_1(char[64]), true> Any; 在上面第7节描述的非限定访问(non-strict visitation)能保证对自由Variant实例进行真确的优美的脱散。
10、总结 这篇文章介绍了可识别联合完整的泛型实现。这个实现展示的一系列特性能灵活地适当地为大量的应用服务。 Variant的基本特点是,有指定可能存放类型集的能力。无差别地支持基本类型和用户自定类型,通过用函数指针和避免分配自由存储区来获得高效率。一个功能丰富的脱散模式,和一个强大的类型转换系统。
11、致谢 我(Andrei Alxanderascu)要感谢Thant Tessman,为了他的彻底的检查。
参考书目 [1]F.Cacciola."An Improved Variant Type Based on Membe Templates"(C/C++ Users Journal,October 2000) [2]Volker Simonis "Chameleonic Objects"(C++ Report, January 2000) [3]A.Alexandrescu."Modern C++ Design"(Addison-Wesley,2001) [4]H.Sutter."Exceptional C++"(Addison-wesley,2000) [5]E.Gamma,et al"Design Patterms"(Addison-Wesley,1994) [6]R.Martin,et al."Pattern Languages of Program Design"(Addison-Wesley,1997)p.93 [7]K.Czarnecki,U.Eisenecker."Metalisp"(http://home.t-online.de/home/Ulrich.Eisenecker/meta.htm,1998) [8]K.Czarnecki."Generative Programming:Principles and Techniques of Software Engineering Based on Automated Configuration and Fragment-Based Component Modes"(Ph.D.Thesis,University of Ilmenau,Germany,1998,http://www.prakinf.tu-ilmenau.de/~czarn/diss) [9]K.Czarnecki,U.Eisenecker."Generative Programming"(Addison-Wesley,2000)

|