用于包装 SDL 和 OpenGL 绘图方法的虚拟函数的替代方案
Posted
技术标签:
【中文标题】用于包装 SDL 和 OpenGL 绘图方法的虚拟函数的替代方案【英文标题】:Alternatives to virtual functions for wrapping SDL and OpenGL drawing methods 【发布时间】:2011-03-08 03:43:34 【问题描述】:我正在尝试用 C++ 为游戏编写一些样板代码。我想同时使用 SDL 和 OpenGL 进行绘制。例如,我想有 2 个版本的函数“DrawPixel”用 OpenGL 和 SDL 编写。我不能同时使用 SDL 和 OpenGL 进行渲染,但是我想要一个通用的绘图接口,这样我在实现游戏时就不必编写两倍的代码了。
我可以编写一个基类,其中包含我想要的所有虚拟绘图功能,然后为 SDL 和 OpenGL 重载它的 2 个版本。问题在于性能,因为我可能最终每秒调用 DrawPixel 数百万次,解决虚拟调用会很昂贵。
除了这种 OO 方法,我还有哪些其他选择? (或者你可以告诉我虚拟并不慢)
【问题讨论】:
【参考方案1】:在我回答您的问题之前,您是否考虑过将其设置为编译时标志,例如#define -- 这将让您决定是 GL 还是 SDL,而不会浪费空间或需要虚拟调用。如果可以的话,一定要选择这个解决方案。
虚拟函数只是一个额外的指针查找,调用一个指针引用的函数而不是一个常量值。 - 它们有一个很大的缺点,即它们不能被内联,因为编译器不知道函数在哪里。这将是任何在运行时决定函数的程序的问题。
首先我要警告:这些可能是不值得担心的微优化。我可能会考虑进行性能基准测试,看看这有多重要。例如,GL 和 SDL 调用已经在一个单独的库中,因此它们中的每一个都可能已经需要指针查找等。额外的指针可能是您最不关心的问题。
如果您真的无法在编译时做出决定,恕我直言,避免虚拟调用的唯一简单方法是使用 C++ 模板来完成这项工作。在您的情况下,我会为您需要的每个 GL 或 SDL 函数创建两个单独的类,它们的名称相同,但不要使它们成为虚拟的。然后,整个程序中使用这些函数的每个函数/类都需要使用 SDL 或 GL 声明为模板。最后,在您的*** main() 函数中,有一个 if 语句,它根据您想要的版本运行程序的一个或另一个版本。
这将导致一个非常有效的解决方案,甚至内联所有可以内联的函数;但是,代价是拥有两个程序副本并占用更多空间。另外——因为它会产生一个更大的程序,这实际上可能会降低性能。
如果您不想要模板,一种可能也有效的奇怪方法是让加载程序使用两个不同的动态链接库之一。两个库都将导出相同的函数,然后您可以选择加载其中一个(例如,通过将 LD_LIBRARY_PATH 设置为一个目录)
【讨论】:
只要只执行其中一个代码路径,额外的代码大小通常不会造成问题。未使用的路径要么根本不会被交换到内存中,要么仅与另一条路径共享一个页面,但不会成为障碍。 如果您在单个DEFINE上切换模板类选择,未使用的代码将不会被编译,因此根本不会占用任何空间。【参考方案2】:虽然我确实同意#defines 和模板是一种更“干净”的编译时处理方式,但纯粹是轶事:
如果现代编译器能够证明虚函数调用只能与一个类相关,而不能与另一个类相关(例如,如果您创建了一个 A 类型的对象并且从不通过另一种类型)。您还可以使用范围解析运算符(例如 A::draw())显式告诉编译器此信息。
您还应该知道,在任何一种情况下,大多数 GL 函数都是“虚拟的”(因为您通过函数指针访问它们,这有点像),而 virtual 函数不是非内联函数的问题如此之多。现代 CPU 已针对虚函数调用的“虚拟”部分进行了优化,并且可以很好地处理它们,但这些函数仍然没有内联,这必然会使它们变慢(但并不比其他非内联函数慢多少)。
一般来说,对于编译器无法内联的小函数,“每秒数百万次”并不是一个好主意。
如果您想在游戏中绘制数百万像素,您最好绘制一个带纹理的四边形,它是两个或三个函数调用一起并在硬件中运行。逐个绘制像素通常不是我们想要做的。
【讨论】:
以上是关于用于包装 SDL 和 OpenGL 绘图方法的虚拟函数的替代方案的主要内容,如果未能解决你的问题,请参考以下文章