潘凯:C 对象布局及多态实现的探索(十二)

来源:百度文库 编辑:神马文学网 时间:2024/04/30 13:08:23
 潘凯:C++对象布局及多态实现的探索(十二)     
后记
结合前面的讨论,我们可以看到,只要牵涉到了虚继承,在访问父类的成员变量时生成的代码相当的低效,需要通过很多间接的计算来定位成员变量的地址。在指针类型转换,动态转型,及虚函数调用时,也需要生成很多额外的代码来调整this指针。象前一篇中对C170对象的obj.foo()和obj.f170()两次调用,传递到两个函数中的this指针居然是不一样的。
前面我们碰到过的怪异行为还有很多,比如偏移值指针指向地址的前4字节,及C150、C170对象中的4字节0值的语义,为什么对C150和C170调用foo函数时,this指针指向的不是子类部分的起始位置而是祖父类的起始位置,等等。去彻底的探究这些问题的意义并不是很大。虚继承的实现属于编译器厂商的行为,厂商出于不同的考虑,实现的方法也会大相径庭。
对于传统的C程序员,他们可能会认为C++的效率低。其实效率低是低在多态部分,因为这要在运行时通过虚表来决议出函数的地址。但对于设计而言,多态是一个非常强大的武器。多态也是面向对象设计的核心技术之一。虽然在执行的效率上有所损失,但对于大规模的程序设计,对于问题域到模型的映射,使用以多态为核心的面向对象设计技术可以提高设计、实现及维护的效率,对于大部分的应用,总体来说得大于失。
但是对于虚继承,个人感觉只是为了解决菱形继承及更复杂继承问题不得已而引入的一项机制,而且没有完美的解决方案,不但大幅的损失效率,而且带来了巨大的复杂性,使得继承结构晦涩难懂。如非万不得已,且在自己清楚一切后果的情况下,建议不要使用。尤其是不要在被虚继承的基类中声明非静态的成员变量。
C++支持多种编程范型,面向过程式的、数据抽象及封装、面向对象、现在又多了一种基于模板技术的泛型编程。我们以一个优秀的开源C++编程环境ACE(之所以叫编程环境,因为它提供了从类库到框架的多层次的支持)为例,看看在设计时的衡量及各种编程范型的运用契机。ACE分几个层次,依次为:OS适配层、wrapper facade层、框架层、服务组件层。OS适配层、wrapper facade层主要运用了数据抽象及封装,没有用到多态及虚机制。因为这两层的关键是效率。而且在这两层中的类的成员方法尽量的内联。其实不使用多态及虚机制的话,C++的效率和C应该是差不多的,但对象封装导致了大量的琐碎方法(如对成员变量的访问封装,即set,get方法),而方法调用的成本也是相当高昂的(需要保存及恢复当前的执行环境上下文,参数的传递及返回值可能产生很多的临时变量及对象等)。所以这两层通过内联来减少函数调用的开销,提高执行效率。在框架层则使用了大量的设计模式,大量使用了多态机制及泛型技术。在这一层的主要关注点是结构的清晰,及实现设计上的语义。在这时多态机制在执行效率上的损失是可以忽略不计的。
最后,我用Lippman在他的经典书籍《Inside the C++ Object Model》中关于描述虚成员函数章节中的一段话来做为这系列文章的结束。“Although I have a folder full of examples worked out and more than one algorithm for determining the proper offsets and adjustments, the material is simply too esoteric to warrant discussion in this text. My recommendation is not to declare nonstatic data members within a virtual base class. Doing that goes a long way in taming the complexity.”大意为在虚继承时用以确定偏移地址及进行this指针调整的可行算法很多,而且大都非常的诡异(这个我们已经见识了:))。同时他建议不要在被虚继承的基类中声明非静态的成员变量,这样做纯属自取烦恼。
另,感谢张水松和张建业这两个土人,在写这些文章时和他们进行了一些非常有益的探讨。最后也是他们提醒我,不要再深入下去,以免走火入魔。
(全文完)