继承、组合和多个成员变量的优缺点

3

我正在研究Ogre3D代码和WildMagic代码,发现它们处理核心类的方式有些不同。由于我正在创建自己的核心,我想知道哪种方式是更好的实践,并且在资源方面可能更好。

在WildMagic中,有一个从Table类继承的Matrix类(不是多态的)。表类可以有N行和N列,并获得列和行的getter和setter。然后,Matrix类具有仅对矩阵有意义的功能,并方便地从Table继承。在这种情况下,向量也可以从表中继承(尽管WildMagic没有这样做)。WildMagic还有一个Transform对象,用于存储节点的本地和派生变换。因此,一个节点将有两个包含必要转换(包括位置、旋转、比例)的Transform对象。

另一方面,在Ogre3D中,Matrix类不继承任何东西,而Ogre3D的节点有变量来存储本地和派生位置:Vector localPos; Vector derPos; Matrix localRot; Matrix derRot; 等等

现在请记住,这些是核心对象,每帧将使用/更新/修改数千次,如果您意识到在完全依赖这些核心类的游戏中存在性能瓶颈,则相当难以更改它们。

所以现在的问题是:

  1. Matrix类从Table类继承是否会有成本,与Matrix类一开始就完成所有工作相比?
  2. 在Node类中有3个Transform对象实例与通过成员变量公开底层数据类型之间是否有成本?(我意识到在两种情况下,都是组合,只是在一种情况下,有一个用于转换的"包装器",而在另一种情况下,转换直接公开。我想这个问题可以重新表述为:一个包装器是否有成本(考虑到它是需要创建和销毁的对象)?
  3. 假设我正在编写一个应用程序,每个计算(甚至是简单的向量加法)都很重要。您会改变您的选择吗?
3个回答

2
非常好,这是一个有许多答案的复杂问题。我会尽力回答,但至少在我看来,整体答案更多取决于您的需求而不是单个人可能做的事情。
1)继承总是有成本的,尽管非常微小。对于这个问题,没有简单的答案,所以我建议阅读:http://www.hackcraft.net/cpp/templateInheritance/ 2)任何需要创建或销毁的东西都有成本。通常包装可以让项目中的大量人员更加“安全”,但除此之外,我并不觉得它有用或值得。就像继承一样,包装的成本非常小。
3)如果每个计算都很重要,那么如果不必要,就不要包装它们。至少对于简单的数学类,只需使用模板即可轻松使用,而无需继承。
总的来说,对于矩阵和向量的基本数据结构,无论采用哪种方法,在PC或控制台上都不会使程序崩溃。慢速内存管理器和内存分配将比为向量和矩阵类继承而导致的程序运行缓慢更加危险。

不错的答案。也许还有一些东西可以补充,我在一次关于控制台编程的演示中看到:您可能还想考虑缓存性能(像cachegrind这样的工具可以告诉您是否值得研究),并且同质数据布局可能会提高缓存性能。如果所有矩阵数据都落在一个漂亮的矩形内存块中,那很好,但是如果它被许多其他不需要进行核心计算的小类变量所插入,您可能会破坏缓存性能。有时过度使用面向对象编程可能会产生负面影响...但在优化之前一定要检查! - Kerrek SB
1
如果你链接的文章给人留下了继承总是有代价的印象,那么我写作时就失败了。继承并不总是有代价的,而且通常情况下,类可以与纯独立类编码完全相同。(甚至调用虚成员也不总是有代价的)。对于像问题描述的非多态继承,代价为零的可能性很大。 - Jon Hanna

0

如果你没有进行虚函数调用,那么从纯性能角度来看,这并不重要。我个人会不赞成继承而更倾向于其他解决方案。小型包装对象将被编译器优化掉。然而,我并不会鼓励你把OGRE3D视为良好设计的顶峰——它们充斥着单例模式。


哈哈,不幸的是,很多游戏代码都是这样的!单例在这里的社区中通常被反对(我必须补充说有非常充分的理由),但它们在生产游戏引擎中被广泛使用。 - Samaursa

0
对于(1),你确定没有多态性吗?虚函数调用是继承中最大的效率损失。如果没有虚函数,两者之间几乎没有区别,绝对没有可以概括的区别。也许对象在一个案例中与另一个案例中的内存中布局不同,也许你会有不同的大小,在其中一个案例中浪费更多的缓冲区等等。
对于(2),一切都取决于代码被优化的可能性。通常,如果相关函数都很容易内联,那么在轻量级包装器情况下就不会有太多实际的函数调用。但是,对于诸如内存布局之类的事情可能会产生后果。这在抽象中很难预测。
对于(3),我还没有做出选择,所以我不知道你指的是什么 :-P

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接