深层继承树(在c++中)是否存在效率劣势,即一组大量的类A、B、C等,其中B继承自A,C继承自B,以此类推。我能想到的一个效率影响是,当我们实例化最底层的类,比如C时,B和A的构造函数也会被调用,这将带来性能影响。
深层继承树(在c++中)是否存在效率劣势,即一组大量的类A、B、C等,其中B继承自A,C继承自B,以此类推。我能想到的一个效率影响是,当我们实例化最底层的类,比如C时,B和A的构造函数也会被调用,这将带来性能影响。
让我们列举需要考虑的操作:
每个构造函数/析构函数都会调用其基类的等效函数。然而,正如James McNellis所指出的那样,你显然要做这些工作。你从A派生并不是因为它在那里。所以这项工作总会以某种方式完成。
是的,这将涉及更多的函数调用。但与任何深层次的类层次结构实际上必须进行的工作相比,函数调用开销微不足道。如果您到了函数调用开销实际上很重要的性能点,我强烈建议您在代码中可能不想做任何构造函数调用。
一般来说,派生类的开销为零。虚拟成员的开销是一个指针或者虚继承。
通过这种方式,我指的是调用非虚拟成员函数,或者使用类名(ClassName::FunctionName语法)调用虚拟成员函数。这两种方法都允许编译器在编译时知道要调用哪个函数。
这个性能与层次结构的大小无关,因为它是在编译时确定的。
这是指完全期望运行时调用虚拟函数。
在大多数合理的C++实现下,这与对象层次结构的大小无关。大多数实现使用每个类的虚函数表(v-table)。每个对象都有一个作为成员的v-table指���。对于任何特定的动态调用,编译器访问v-table指针,挑选出方法并调用它。由于v-table对于每个类都是相同的,因此对于具有深层次结构的类而言,速度不会比具有浅层次结构的类慢。
虚拟继承会稍微影响一下。
这指的是static_cast
或任何等效操作。这意味着从派生类到基类的隐式转换,显式使用static_cast
或C风格转换等。
请注意,这在技术上包括引用转换。
静态类型转换(向上或向下)的性能与继承层次的大小无关。任何指针偏移将在编译时生成。这对于虚拟继承和非虚拟继承都应该成立,但我不是100%确定。
这显然指的是显式使用dynamic_cast
。这通常用于从基类向派生类进行强制类型转换。
dynamic_cast
的性能可能会因继承层次的大小而改变。但是,合理的实现应只检查当前类和请求类之间的类。因此,它仅与两者之间的类数量成线性关系,而不是与继承层次中的类数量成线性关系。
这意味着使用typeof
运算符获取与对象相关联的std::type_info
对象。
这个操作的性能与继承层次的大小无关。如果类是虚拟类(具有虚函数或虚基类),则它将从虚表中提取出来。如果它不是虚拟的,则它在编译时被定义。
简而言之,大多数操作与继承层次的大小无关。但是,即使在有影响的情况下,这也不是问题。
我更关心的是您是否需要构建这样的层次结构的设计理念。根据我的经验,这样的层次结构来自两条设计线路。
Java/C#理想中的让所有东西都从一个共同的基类派生出来。这在C ++中是一个可怕的想法,不应该使用。每个对象应该只从它需要的基类派生,并且仅从它需要的基类派生。 C ++是建立在“按需付费”原则上的,而从一个共同的基类派生的做法与此相反。一般来说,您可以使用函数重载(例如使用operator<<
转换为字符串)来执行与此类共同基类相关的任何操作,或者您本来就不应该进行此类操作。
滥用继承。当你应该使用包含时使用继承。继承在对象之间创建了一个“是一个”关系。往往,“有一个”关系(一个对象作为成员拥有另一个对象)更加实用和灵活。它们使数据隐藏更容易,并且不允许用户假装一个类是另一个类。
确保你的设计不违反这些原则。
这会影响程序员的工作表现,但不会像以前那样糟糕。
正如@Nicol所指出的,它可能正在执行许多操作。
如果这些是您需要完成的操作,无论设计如何,因为它们都是在最少的周期内将程序从call main
转换为exit
所必需的精确步骤,则您的设计仅仅是编码清晰度的问题(或者可能是缺乏它:)。
根据我的经验,在性能调优例如此示例中,我经常看到的一个巨大的时间浪费源是数据(即类)结构的过度设计。 奇怪的是,数据结构的理由通常是(猜猜看?)- 性能!
在我的经验中,处理数据结构的关键是尽可能保持简单和规范化。如果它完全规范化,那么对它进行任何单一更改都不会使其不一致。你并不总能实现完全的规范化,在这种情况下,你必须处理数据可能暂时不一致的可能性。数据结构与巨大的低效率相伴而行的另一种方式是,如果数据被某个过程有效地解释以产生某些输出,这在图形方面非常常见。 如果数据变化非常缓慢,则将其“编译”而不是“解释”可能是有意义的。 换句话说,将其转换为更简单的指令集或源代码,然后即时编译它,这样就可以更快地执行以产生所需的输出。