本文来源于TopLanguage Group 上的一次讨论(这里 ,这里 和这里 )。pongba提出:C++的抽象机制并不完善,原因是为了性能而做的折中,未来随着计算能力的提高到一定程度,人们就能够忽略更好的抽象所带来的负面效应。就此诸老大各自提出高见,受益良多啊。经过讨论,我基本上理解了pongba的想法。但我觉得等待计算机的性能提高太消极了。我相信随着编程技术的发展,这种最优抽象造成的性能损失将会越来越小。这种途径将会更快地让人们接受最优抽象形式。
在“C++ Template”一书中,将多态总结为三种主要类型:runtime bound、static unbound和runtime
unbound。其中runtime bound就是我们通常所说的动多态,OOP的核心支柱(广义上OOP还包括Object
Base(OB,仅指类型封装等OO的基本特性),但有时也会将OB和OOP分开,OOP单指以OO为基础的动多态。这里使用狭义的OOP含义);
static unbound就是静多态,通过模板实现。而runtime
unbound则是一种不常见的形式。早年的SmallTalk具有这种形式,现在的ruby也引入这种机制。
在主流的(静态)语言中,我们会面临两种类型的多态需求:对于编译期可以确定类型的,使用静多态,比如实例化一个容器;对于运行期方能确定类型的,则使用
动多态。而runtime
unbound也可以用于运行期类型决断。于是,便有了两种运行期多态。这两种多态的特性和他们的差异,是本文的核心。实际上,相比动多态,
runtime unbound多态为我们提供了更本质的运行时多态手段,我们可以从中获得更大的收益。但是鉴于一些技术上的困难,runtime
unbound多态无法进入主流世界。不过,由于新的编程技术的出现,使得这种更好的运行时多态形式可以同动多态一比高下。
动多态
废话少说,让我们从一个老掉牙的案例开始吧:编写一个绘图程序,图形包括矩形、椭圆、三角形、多边形等等。图形从脚本(比如xml)中读出,创建后保存在一个容器中备查。通过遍历容器执行图形绘制。
就这么个题目,很简单,也很熟悉,解释OOP的动多态最常用的案例。下面我们就从动多态实现开始。
首先定义一个抽象基类,也就是接口:
class IShape
{
virtual void load(xml init)=0;
virtual void draw(monitor m)=0;
...
};
然后定义各种图形类,并从这个接口上继承:
class Rectangle: public IShape
{
void load(xml init) {...}
void draw(monitor m) {...}
...
};
class Ellipse: public IShape
{
void load(xml init) {...}
void draw(monitor m) {...}
...
};
...
void DrawShapes(monitor m, vector<IShape*> const& g)
{
vector<IShape*>::const_iterator b(g.begin()), e(g.end());
for(; b!=e; ++b)
{
(*b)->draw(m);
}
}
...
现在可以使用这些图形类了:
vector<IShape*> vg;
vg.push_back(new Rectangle);
vg.push_back(new Ellipse);
...
DrawShapes(crt, vg);
通过接口IShape,我们可以把不同的图形类统一到一种类型下。但是,通过虚函数的override,由图形类实现IShape上的虚函数。这可以算老
生常谈了。动多态的核心就是利用override和late
bound的组合,使得一个基类可以在类型归一化的情况下,拥有继承类的语义。OOP设计模式大量运用这种技术,实现很多需要灵活扩展的系统。
Runtime Unbound
Runtime Unbound多态混合了静多态和动多态的特征,即既有类型泛化,又是运行时决断的。一个最典型的例子就是ruby的函数:
class x
def fun(car) car.aboard end end
这个案例非常明确地展示出了Runtime Unbound多态的特点。car参数没有类型,这里也不需要关心类型,只要求car对象有一个aboard方法即可。由于ruby是动态语言,能够运行时检测对象的特征,并动态调用对象上的方法。
在Runtime Unbound的思想指导下,我们利用一种伪造的“动态C++”,把上面的绘图例子重新编写:
class Rectangle
{
void load(xml init) {...}
void draw(monitor dev) {...}
...
};
class Ellipse
{
void load(xml init) {...}
void draw(monitor dev) {...}
...
};
...
void DrawShapes(monitor dev, vector<anything> const& g)
{
vector<IShape>::const_iterator b(g.begin()), e(g.end());
for(; b!=e; ++b)
{
(*b).draw(dev);
}
}
...
vector<anything> vg;
vg.push_back(Rectangle(...));
vg.push_back(Ellipse(...));
...
DrawShapes(crt, vg);
图形类不再从抽象接口IShape继承,而用关键字anything实例化vector<>模板。这个虚构的anything关键字所起的作
用就是使得vector能够接受不同类型的对象。当DrawShapes()函数接收到存放图形对象的容器后,遍历每一个对象,并且调用对象上的draw
()函数,而不管其类型。
从这段代码中,我们可以看出Runtime
Unbound多态带来的好处。所有图形类不再需要归一化成一个类型(抽象接口)。每个类只需按照约定,实现load、draw等成员函数即可。也就是
说,这些图形类解耦合了。一旦类型解耦,便赋予我们很大的自由度。最典型的情况就是,我们需要使用一个其他人开发的图形类,并且无法修改其实现。此时,如
果使用动多态,就很麻烦。因为尽管这些图形类都拥有load、draw等函数,但毕竟不是继承自IShape,无法直接插入容器。必须编写一个继承自
IShape的适配器,作为外来图形类的包装,转发对其的访问。表面上,我们只是减少一个接口的定义,但Runtime
Unbound多态带来的解耦有着非凡的意义。因为类耦合始终是OOP设计中的一个令人头痛的问题。在后面,我们还将看到建立在Runtime
Unbound多态基础上的更大的进步。
然而,尽管Runtime Unbound多态具有这些优点,但因为建立在动态语言之上,其自身存在的一些缺陷使得这项技术无法广泛使用,并进入主流。
Runtime Unbound多态面临的第一个问题就是类型安全。确切的讲是静态类型安全。
本质上,Runtime
Unbound多态(动态语言)并非没有类型安全。当动态语言试图访问一个未知类型对象的成员时,会通过一些特殊机制或特殊接口获得类型信息,并在其中寻
找所需的对象成员。如果没有找到,便会抛出异常。但是,传统上,我们希望语言能够在编译期得到类型安全保证,而不要在运行时才发现问题。也就是说,
Runtime Unbound多态只能提供运行时类型安全,而无法得到静态类型安全。
第二个问题是性能。Runtime Unbound需要在运行时搜寻类型的接口,并执行调用。执行这类寻找和调用的方法有两种:反射和动态链接。
反射机制可以向程序提供类型的信息。通过这些信息,Runtime Unbound可以了解是否存在所需的接口函数。反射通常也提供了接口函数调用的服务,允许将参数打包,并通过函数名调用。这种机制性能很差,基本上无法用于稍许密集些的操作。
动态链接则是在访问对象前在对象的成员函数表上查询并获得相应函数的地址,填充到调用方的调用表中,调用方通过调用表执行间接调用。这种机制相对快一些,但由于需要查询成员函数表,复杂度基本上都在O(n)左右,无法与动多态的O(1)调用相比。
这些问题的解决,依赖于一种新兴的技术,即concept。concept不仅很消除了类型安全的问题,更主要的是它大幅缩小了两种Runtime多态的性能差距,有望使Runtime Unbound成为主流的技术。
concept
随着C++0x逐渐浮出水面,concept作为此次标准更新的核心部分,已经在C++社群中引起关注。随着时间的推移,concept的潜在作用也在不断被发掘出来。
concept主要用来描述一个类型的接口和特征。通俗地讲,concept描述了一组具备了共同接口的类型。在引入concept后,C++可以对模板参数进行约束:
concept assignable<T> {
T& operator=(T const&);
}
template<assignable T> void copy(T& a, T const& b) {
a=b;
}
这表示类型T必须有operator=的重载。如果一个类型X没有对operator=进行重载,那么当调用copy时,便会引发编译错误。这使得类型参数可以在函数使用之前便能得到检验,而无需等到对象被使用时。
另一方面,concept参与到特化中后,使得操作分派更加方便:
concept assignable<T> {
T& operator=(T const&);
}
concept copyable<T> {
T& T::copy(T const&);
}
template<assignable T> void copy(T& a, T const& b) { //#1
a=b;
}
template<copyable T> void copy(T& a, T const& b) { //#2
a.copy(b);
}
X x1,x2; //X支持operator=操作符
Y y1,y2; //Y拥有copy成员函数
copy(x1, x2); //使用#1
copy(y1, y2); //使用#2
在静多态中,concept很好地提供了类型约束。既然同样是Unbound,那么concept是否同样可以被用于Runtime
Unbound?应当说可以,但不是现有的concept。在Runtime Unbound多态中,需要运行时的concept。
依旧使用绘图案例做一个演示。假设这里使用的"C++"已经支持concept,并且也支持了运行时的concept:
class Rectangle
{
void load(xml init) {...}
void draw(monitor dev) {...}
...
};
class Ellipse
{
void load(xml init) {...}
void draw(monitor dev) {...}
...
};
...
concept Shape<T> {
void T::load(xml init);
void T::draw(monitor dev);
}
...
void DrawShapes(monitor dev, vector<Shape> const& g)
{
vector<IShape>::const_iterator b(g.begin()), e(g.end());
for(; b!=e; ++b)
{
(*b).draw(dev);
}
}
...
vector<Shape> vg;
vg.push_back(Rectangle(...));
vg.push_back(Ellipse(...));
vg.push_back(string("xxx")); //错误,不符合Shape concept
...
DrawShapes(crt, vg);
乍看起来没什么特别的,但是请注意vector<Shape>。这里使用一个concept,而不是一个具体的类型,实例化一个模板。这里的意思是说,这个容器接受的是所有符合Shape concept的对象,类型不同也没关系。当push进vg的对象不符合Shape,便会发生编译错误。
但是,最关键的东西不在这里。注意到DrawShapes函数了吗?由于vector<Shape>中的元素类型可能完全不同。语句
(*b).draw(dev);的语义在静态语言中是非法的,因为我们根本无法在编译时具体确定(*b)的类型,从而链接正确的draw成员。而在这里,
由于我们引入了Runtime Unbound,对于对象的访问链接发生在运行时。因此,我们便可以把不同类型的对象存放在一个容器中。
concept在这里起到了类型检验的作用,不符合相应concept的对象是无法放入这个容器的,从而在此后对对象的使用的时候,也不会发生类型失配的
问题。这也就在动态的机制下确保了类型安全。动多态确保类型安全依靠静态类型。也就是所有类型都从一个抽象接口上继承,从而将类型归一化,以获得建立在静
态类型系统之上的类型安全。而concept的类型安全保证来源于对类型特征的描述,是一种非侵入的接口约束,灵活性大大高于类型归一化的动多态。
如果我们引入这样一个规则:如果用类型创建实例(对象),那么所创建的对象是静态链接的,也就是编译时链接;而用concept创建一个对象,那么所创建的对象是动态链接的,也就是运行时链接。
在这条规则的作用下,下面这段简单的代码将会产生非常奇妙的效果:
class nShape
{
public:
nShape(Shape g, int n) : m_graph(g), m_n(n) {}
void setShape(Shape g) {
m_graph=g;
}
private:
Shape m_graph;
int m_n;
};
在规则的作用下,m_graph是一个动态对象,它的类型只有在运行时才能明确。但是无论什么类型,必须满足Shape concept。而m_n的类型是确定的,所以是一个静态对象。
这和传统的模板有区别吗?模板也可以用不同的类型参数定义成员数据。请看如下代码:
Rectangle r;
Ellipse e;
nShape(r, 10);
nShape.setShape(e); //对于传统模板而言,这个操作是非法的,因为e和r不是同一种类型
动态对象的特点在于,我们可以在对象创建后,用一个不同类型的动态对象代替原来的,只需要这些对象符合相应的concept。这在静态的模板上是做不到的。
现在回过头来看一下用concept实例化模板的情形。我们知道,用一个类型实例化一个模板,得到的是一个类,或者说类型。而用一个concept实例化
一个模板,得到的又是什么呢?还是一个concept。那么vector<Shape>是一个concept,因而它的实例是动态的对象。当
然,实际上没有必要把vector<Shape>的实例整个地当成动态对象,它只是具有动态对象的行为特征。在实现上,vector<
Shape>可以按照普通模板展开,而其内部由concept模板实参定义的对象作为动态对象处理即可。
一个由concept实例化的模板的对象作为语义上的动态对象。
下面的代码则引出了另一个重要的特性:
vector<
float> vFloat; //静态对象的容器,内部存放的都是静态对象,属于同一类型float
vector<
Shape> vShape; //动态对象的容器,内部存放动态对象,都符合Shape
同一个类模板,当
使用类型实例化,执行static unbound多态;
使用concept实例化,执行runtime unbound多态。两者的形式相同。也就是说
static多态同runtime多态以相同的形式表达。
由于concept的加入,两种完全不同的多态被统一在同一个模型和形式下。实际上,static和runtime
unbound多态可以看作同一个抽象体系的两个分支,分别处理不同情况的应用。而形式上的统一,则更加接近抽象体系的本质。同时,也使得两种
unbound多态的差异被后台化,使用者无需额外的工作,便可以同时获得动态和静态的抽象能力。同时,两种多态所展示的逻辑上的对称性,也暗示了两者在
本质上的联系。这里统一的形式,便是这种对称性的结果。
对于模板函数,则会表现出更加有趣的特性(这个函数模板有些特别,不需要template关键字和类型参数列表,这是我伪造的。但由于concept的使用,它本质上还是一个模板):
void draw(
Shape g);
这个函数接受一个符合Shape的参数。如果我们用一个静态对象调用这个函数:
Rectangle r;
draw(r);
那么,就执行static unbound,实例化成一个完完整整的函数,同传统的函数模板一样。
如果用一个动态对象调用这个函数:
Shape g=Cycle(); draw(
g);
g=Rectangle();
draw(
g);
那么,就执行runtime unbound,生成一个等待运行时链接的函数。上面的两次调用,分别进行了两次运行时链接,以匹配不同的动态对象。
这样,我们可以通过函数调用时的参数对象,来控制使用不同的多态形式。更复杂的情况就是用一个函数的返回值调用另一个函数,这样构成的调用链依然符合上述的调用控制原则。
下面,我们将看到Runtime Unbound多态的一个精彩表演:
//假设,我们已经定义了Rectangle、Cycle、Square、Ellipse、Trangle五个类,
// 分别map到Rectangles、Cycles、Squares、Ellipses、Trangles五个concept上,
// 这些concept都refine(可以不正确地理解为继承吧)自Shape。
void draw(monitor dev,
Rectangles r); //#3
void draw(monitor dev,
Cycles c); //#4
void draw(monitor dev,
Squares s); //#5
void draw(monitor dev,
Ellipses e); //#6
void draw(monitor dev,
Trangles t); //#7
//此处定义一个Shape的动态对象
Shape g=
CreateShapeByUserInput(); //这个函数根据用户输入创建图形对象,所以图形对象的类型只能到运行时从能确定。
draw(crt, g);
好了,现在该调用哪个版本的draw?根据用户的输入来。换句话说,调用哪个版本的draw,取决于
CreateShapeByUserInput()函数的返回结果,也就是用户输入的结果。如果
CreateShapeByUserInput()
返回Rectangle的动态对象,那么执行#3;如果返回的是Trangle对象,那么执行#7。这是一种动态分派的操作。在运行时concept的作
用下,实现起来非常容易。对draw的调用最终会被转换成一个concept需求表,来自draw函数,每一项对应一个函数版本,并且指明了所对应的
concept。动态对象上也有一个concept表,每一项存放了这个对象所符合的concept。用这两个表相互匹配,可以找到g对象的
concept最匹配的那个draw版本,然后调用。
这实际上是将重载决断放到运行时进行,而concept在其中起到了匹配参数的作用。
这样的做法同利用rtti信息执行类型分派调用类似:
void draw_impl(monitor dev, Rectangle& r);
void draw_impl(monitor dev, Cycle& c);
void draw_impl(monitor dev, Square& s);
void draw_impl(monitor dev, Ellipse& e);
void draw_impl(monitor dev, Trangle& t);
void draw_impl(monitor dev, Shape& g) {
if(typeif(g)==typeid(Rectangle))
draw_impl(dev, (Rectangle&)g);
else if(typeif(g)==typeid(Cycle))
draw_impl(dev, (Cycle&)g);
...
}
但是,他们却有着天壤之别。首先,rtti分派是侵入的。如果需要增加一个图形,需要在draw函数中增加分派代码。而Runtime Unbound方案则只需要用新的concept重载draw函数即可。
其次,rtti版本有多少图形类,就需要多少if...else...,而Runtime
Unbound则是一对多的。如果有几个图形类内容不同,但有相同的接口,符合同一个concept,那么只需针对concept编写一个函数版本即可。
比如,如果有一个特别的CycleEx类,使用外界正方形的左上角/右下角坐标描述,正好符合Ellipses
concept,那么只需将CycleEx map到Ellipses上即可,无需多加任何代码。
最后,rtti需要获取类型信息,然后做线性比较,性能无法优化。但Runtime Unbound通过concept表的相互匹配,仅牵涉数值操作,有很大的优化空间。
那么这样一种运行时分派有什么好处呢?我们看到图形类上的draw函数接受一个monitor类型参数,它代表设备。如果哪一天需要向另一种设备,比如
printer,输出图形,那么就需要在图形类上增加另一个版本的draw函数。如果类是别人开发的,那么就增加沟通的负担。如果类是外来的,我们无法修
改,那么只能通过adapter之类的笨拙手段处理。为了让monitor之类同图形本身没有关联的东西分离,应当使用自由函数执行draw操作。但普通
函数只能接受确定的类型重载,而传统的函数模板则限于编译期使用,无法进行运行时分派。所以,如果能够使用concept重载函数,并且赋予
Runtime Unbound机能,那么便可以用最简单的形式针对一类类型进行处理,效能高得多。
运行时concept
语言层面的concept无法做到这些,因为它是编译期机制。为此,我们需要有一种运行时的concept,或者说二进制级别的concept。
一个concept包含了与一个或若干个类型有关的一组函数,包括成员函数和自由函数。于是,我们就可以用一个类似“虚表”的函数指针表(暂且称为
ctable吧)存放concept指定的函数指针。这样的ctable依附在动态对象上,就像vtable一样。每个对象都会匹配和map到若干个
concept。因此,每个动态对象会有一个concept表,其中存放着指向各ctable的指针,以及相应的concept基本信息。
当一个“用户”(函数或模板)需要在运行时链接到对象上的时候,它会提交一个concept的代码(全局唯一)。系统用这个代码在动态对象的
concept表上检索,获得指向所需concept的指针,并且填写到“用户”给出的一个“插入点”(一个指针)中。随后“用户”便可以直接通过这个
“插入点”间接调用所需的函数,成员或自由函数。
在这里,concept的巧妙之处在于,将一族函数集合在一起,作为一个整体(即接口)。那么,在执行运行时匹配的时候,不再是一个函数一个函数地查询,
可以一次性地获知这些函数是否存在。这就很容易地规避了类型安全保证操作的损耗。如果使用hash查询,那么可以在O(1)实现concept匹配。另
外,一个concept的hash值可以在编译时计算好,运行时链接只需执行hash表检索,连hash值计算也可以省去。
一个动态对象可以直接用指向concept ctable的指针表示。在不同concept之间转换,相当于改变指针的指向,这种操作非常类似OOP中的dynamic_cast。
对于如下的动态对象定义:
Shape g=Cycle();
会创建一个Cycle对象,在对象上构建起一个concept表,表中对应Cycle所有符合的concept。并且建立一组ctable,每个
ctable对应一个concept。每个concept表项指向相应的ctable。而符号g则实际上是指向所建立对象的Shapes
ctable的指针。
对于函数:
void draw(Shape g);
draw(g);
调用g时,由于draw的参数是Shape
concept,而g正是draw所需的concept,所以无需在对象g的concept表上匹配,可以直接使用这个ctable指针。这就是说,只要
所用动态对象(g)的concept同使用方(draw函数)能够匹配,便可以直接使用指向ctable的指针链接(编译时链接),无需在运行时重新匹
配。只有发生concept转换时,才需要在concept表中搜索,获得所需的ctable指针:
Swappable s=g; //Swappable是另一个concept
这种情况同dynamic_cast极其相似。也可以模仿着采用concept_cast之类的操作符,使得concept转换显式化,消除隐式转换的问题(强concept化)。
所以,Runtime Unbound在运行时concept的作用下,具有同动多态相同的底层行为。因而,他们的性能也是一样的。很多用于动多态的方案和算法都可以直接用于运行时concept。
Runtime Unbound和Runtime Bound
对于runtime unbound同runtime bound之间的差异前面已经有所展示。在其他方面,两者还存在更多的差别。
首先,就像绘图案例中展示的那样,runtime unbound是非侵入的。runtime unbound不要求类型继承自同一类型,只需将类型同concept关联起来便可。
其次,concept不是一种局限于OO的技术,不仅涉及成员函数,还包括了自由函数,范围更广,更加灵活。
最后,实现上,Runtime Unbound和Runtime
Bound之间有惊人的相似之处。两者都采用一个函数指针表作为操作分派;都采用一个指向函数表的指针作为入口;一个动态对象上的concept之间的转
换,也同动多态对象一样,在不同的函数表间切换。他们唯一的不同,是实现接口的机制。
动多态用类型兼任接口,通过继承和虚函数实现接口的功能。用类型作为类型的接口,使得这两个本来独立的概念交织在一起。增加整个类型体系的复杂度和耦合度。 concept则利用独立的系统描述、表达和管理接口。类型则回归到表达业务对象的功能上来。
动多态在使用类型表达接口的时候,便很容易地引入一个麻烦的问题,表达功能的类型和表达接口的类型混合在一起,使用时必须通过一些方法区分出哪些是接口,
哪些是功能类型。这增加了对象模型的复杂性。而concept则独立于类型体系之外,所有对接口的操作都是单一的,检索和匹配来得更加方便快捷。
作为继承体系的基础部分,动多态的抽象接口必须在继承结构的最顶端。那么这些抽象类型必须先于其他类型出现。这对系统的早期设计产生很大的压力,往往一个基础抽象接口设计有误,便会造成整个体系的变更。
而concept是独立于类型的,那么任何时候都可以将一个类型同接口绑定。接口甚至可以在类型体系基本建立之后才确定。这种灵活性对复杂软件的开发至关重要,去掉了长期以来套在人们头上的枷锁。
前面已经提到,在不需要concept转换的情况下,无需执行运行时的concept匹配,所有的调用具有同动多态一样的效率(都是间接调用)。在执行
concept转换时,无需象动多态那样在复杂的继承体系上检索,只需执行concept表的hash匹配,效率反而更高,而且更简单。考虑到这些情况,
我们可以认为
concept化的Runtime Unbound多态完全能够替代传统的动多态。也就是说,我们
不再需要动多态了。
想象一下,如果一门语言能够拥有运行时concept,那么它完全可以只保留Static Unbound和Runtime
Unbound多态,而放弃Runtime
Bound多态。一旦放弃动多态(没有了虚函数和虚表),那么对象模型便可以大大简化。所有对象只需要线性分布,基类和成员依次堆叠在一起,也没有
vtable的干扰,对象结构可以做到最简单。同时,继承也回归了代码重用的传统用途。而且,对象独立于接口存储,在能够在编译时静态链接的时候,可以作
为静态对象使用。而在需要动态对象的地方,又可以很容易地转换成动态对象,只需要为其附上concept表和ctable。一切都简化了。对象模型也更加
容易统一。
这对于很多底层开发的程序员对于c++复杂而又混乱的对象模型难以接受。如果能够废除虚函数,简化对象模型,那么对于这些底层开发而言,将会带来直接的好
处。只要确保不使用concpt定义对象、实例化模板,便可以使整个软件执行Static
Unbound。这相当于去掉OOP的C++。否则,就启用Runtime Unbound,实现运行时多态。
总结
Static Unbound和Runtime
Unbound作为一对亲密无间的多态技术,体现了最完善的抽象形式。两者各踞一方,相互补充,相互支援。而且两者具有统一的表现形式,大大方便了使用,
对于软件工程具有非凡的意义。另一方面,Runtime
Bound多态作为OO时代的产物,体现了静态类型语言在运行时多态方面的最大努力。但是,随着运行时concept的引入,Runtime
Unbound多态自身存在的静态类型安全问题和性能问题,都能够得到很好的解决。至此,Runtime Unbound便具备了替代Runtime
Bound的实力。相信在不久的将来,Runtime Bound将会逐渐步入它的黄昏。
参考
- http://groups.google.com/group/pongba/web/Runtime+Polymorphic+Generic
+Programming.pdf。大牛人Jaakko Järvi等写的关于Runtime concept的文章,讲解了runtime
concept的概念的实现方法,并在ConceptC++上以库的形式实现。其中使用传统的动多态实现runtime
concept,这表明动多态的实现机制同runtime
concept是一致的。当然库的实现很复杂,这是“螺蛳壳里做道场”,无奈之举。Runtime
concept还是应当在语言中first-class地实现。
- http://www.lubomir.org/academic/MinimizingCodeBloat.pdf。也是Jaakko Järvi写的,运行时分派的文章。
- http://opensource.adobe.com/wiki/index.php/Runtime_Concepts。
- Inside C++ Object Model。
附录 Runtime Concept的具体实现
我们有一个concept:
concept Shape<T>
{
void T::load(xml);
void T::draw(device);
void move(T&);
}
另外,还有一个代表圆的concept:
concept Cycles<T> :
CopyConstructable<T>,
Assignable<T>,
Swappable<T>,
Shape<T>
{
T::T(double, double, double);
double T::getX();
double T::getY();
double T::getR();
void T::setX(double);
void T::setY(double);
void T::setR(double);
}
现在有类型Cycle:
class Cycle
{
public:
Cycle(double x, double y, double r);
Cycle(Cycle const& c);
Cycle& operator=(Cycle const& c);
void swap(Cycle const& c);
void load(xml init);
void draw(device dev);
double getX();
double getY();
double getR();
void setX(double x);
void setY(double y);
void setR(double r);
private:
...
};
当定义一个动态对象:
Shape g=Cycle();
便会形成如下图的结构:
g实际上是一个指针,指向concept表的Shape项,而Shape项指向Shape对应的ctable。由于Cycle
refine自Shape等众多concept,那么Cycle的ctable实际上包含了这些concept的ctable,所以只需一个Cycle的
ctable,而其他concept都分别指向其中各自相应的部分。ctable中的每一个项则指向具体的函数体。
如果遇到语句:
Swappable h=concept_cast<Swappable>(g);
那么,将会执行一个搜索,用concept
Swappable的id(比如hash码)在concept表中检索是否存在Swappable项。如果存在,就将对应项的指针赋给h。这种操作同
dynamic_cast操作非常相似,只是相比在复杂的对象结构中查询更加简单迅速。
concept表置于对象的头部或尾部,这是为了便于对象检索concept接口。每个类型的ctable只需一份。
对象本体可以很容易地同concept表分离,在完全静态的情况下,concept表是不需要的。如果需要runtime多态,加上concept表即可。