我正在考虑创建一个新的C++大项目。开始很容易——只是一个简单的窗口,可能是SDL2,可能是SFML,甚至可能是WIN32。好吧,我该拿什么?用我想用的窗户不是更好吗?在不更改太多代码以便其他类独立于此窗口的情况下?
说了,做了!使用一个简单的窗口接口,每个类都知道类似窗口的东西,我可以在不同的类型之间进行选择。唯一的要求是将IWindow作为基类。
class IWindow {
public:
IWindow(std::string title, int posX, int posY, int width, int height);
IWindow getHandle();
void loop();
bool toggleFullscreen();
bool toggleFullscreen(bool fullscreen);
int getWidth();
int getHeight();
int getPosX();
int getPosY();
//And so on ...
};
但现在,由于我必须使用虚拟方法,每次我的虚拟函数循环都会被游戏循环调用。虚拟功能更慢。大约10%,我读过。
难道编译器不能看到我的窗口是什么吗?它将来自哪种类型?难道它看不到"耶,这个程序员在这个应用程序中创建了一个SDL窗口,所以到处都用它的方法。"?我的意思是,我在主循环中定义我的窗口,它永远不会改变。这不是动态的。这是可以预测的。
那么编译器能够优化我可预测的虚拟函数调用吗?这些将在每个游戏循环中进行评估?就像下面的文章一样?
int main(int argc, char* argv[]) {
//Creates a window derived from IWindow
SDL::Window myWindow("Title", 0, 0, 300, 100);
//Storing it as IWindow in a wrapper class
Game myGame(&myWindow);
//Game loop
//myGame.run() calls the window's loop
while (myGame.run()) {
//... doing game stuff
}
}
像这样的游戏类:
class Game {
protected:
IWindow* window;
public:
bool run() {
//Calls the window's virtual loop method.
//Will it be optimized? Any way to do so?
this->window->loop();
}
};
很高兴听到你的想法和经历。
Darth Moon
C++编译器是否优化虚拟成员调用?
是的,如果编译器能够在编译时确定具体类型,那么它可能能够去虚拟化虚拟函数调用。
不,C++编译器将无法对所有虚拟函数调用进行去虚拟化。
虚拟函数的速度较慢。约10%
假设10%的差异是正确的,请考虑函数调用开销可能在几纳秒的量级上。几纳秒的10%并不多。你可以在像游戏一样的软实时模拟的一次迭代中适应很多纳米秒。
难道编译器不能看到我的窗口是什么吗?
那么编译器能够优化我可预测的虚拟函数调用吗?
也许吧。
首先,对run
的调用必须在分配指针的上下文中内联展开。否则,它无法对指向的对象进行任何假设。为了内联扩展,它必须在与函数调用位置相同的转换单元中定义(LTO可能能够满足此要求(。
此外,编译器必须能够证明window
在执行过程中的任何时候都没有被修改以指向另一个对象。根据循环的外观,这种证明可能是不可能的,但有一种简单的方法可以让它变得容易:声明指针const。
至于你的编译器是否优化了它…我不知道。但你的编译器确实如此,所以我建议把你的问题交给你的编译器(即让它编译你的程序,看看它做什么(。
让我们总结一下我们的评论。
虚拟调用成本很高,但如果处理器能够检测到模式,那么由于现代处理器内部的预测器,它们的调用成本就会降低。
现在,让我们检查您的代码:
int main(int argc, char* argv[]) {
//Creates a window derived from IWindow
SDL::Window myWindow("Title", 0, 0, 300, 100);
//Storing it as IWindow in a wrapper class
Game myGame(&myWindow);
//Game loop
//myGame.run() calls the window's loop
while (myGame.run()) {
//... doing game stuff
}
}
假设Game
有一个虚拟的run
。在这种情况下,编译器知道myGame
的类型是Game
,并且可以直接调用run
函数,而不是遍历虚拟表。
现在你在另一个文件中有了这个:
class Game {
protected:
IWindow* window;
public:
bool run() {
//Calls the window's virtual loop method.
//Will it be optimized? Any way to do so?
this->window->loop();
}
};
不幸的是,在这种情况下,编译器只需查看此文件就无法知道任何信息,因此对SDL::Window
的调用将通过来自IWindow
的虚拟run
。
现在有了lto
(链接时间优化(,编译器可能能够找出它并对代码进行去虚拟化,但可能不会,因为优化选项的数量将随着文件数量和组合数量的增加而增加。