面向对象编程层层抽象造成臃肿,导致运行效率降低,而这是性能要求高的游戏编程领域不想看到的。
面向过程:建立解决问题所需的各个步骤(函数)。
面向对象:建立解决问题所需的各个模型(类)。
面向数据:考虑数据的存取及布局(数据)。
值得一说的是,面向过程和面向对象都是解决问题的一种方法,而面向数据只是一种优化的设计思想,而非解决问题的方法。
冷数据/热数据分割
我们希望CPU缓存存储的是经常使用的数据,而不是那些少用的数据。这就引入了冷数据/热数据分割的概念了。
热数据:经常要操作使用的数据,我们一般可以直接作为可直接访问的成员变量。
冷数据:比较少用的数据,我们一般以引用/指针来间接访问(即存储的是指针或者引用)。
一个例子:对于人类来说,生命值位置速度都是经常需要操作的变量,是热数据。
而掉落物对象只有人类死亡的时候才需要用到,所以是冷数据;
频繁调用的函数尽可能不要做成虚函数
C++的虚函数机制,简单来说是两次地址跳转的函数调用,这对CPU缓存十分不友好,往往命中失败。实际上虚函数可以优雅解决很多面向对象的问题,然而在游戏程序如果有很多虚函数都要频繁调用(例如每帧调用),很容易引发性能问题。
解决方法是,把这些频繁调用的虚函数尽可能去除virtual特性(即做成普通成员函数),并避免调用基类对象的成员函数,代价是这样一改得改很多与之牵连代码。所以最好一开始设计程序时,需要先想好哪些最好不要写成virtual函数。
重新认识C++ STL容器
STL容器,特别是set,map,有着很多O(logN)的操作速度,但并不意味着是最佳选择,因为这种复杂度表示往往隐藏了常数很大的事实。
例如说,集合的主流实现是基于红黑树,基于节点存储的,而每次插入/删除节点都意味着调用一次系统分配内存/释放内存函数。这相比vector等矢量容器所有操作仅一次系统分配内存(理想情况来说),实际上就慢了不少。
此外,矢量容器对CPU缓存更加友好,遍历该种容器容易命中缓存,而节点式容器则相对容易命中失败。
综合上述,如果要选择一个最适合的容器,那么不要过度信赖时间复杂度,除非你十分彻底的了解STL容器,或对各容器进行多次效率测试。
总结
对面向对象和面向数据的看法:应该兼有。
因为游戏程序是一个既需要高性能又复杂的工程。使用面向对象的游戏程序新手,常常就有一个问题:过度设计/过度抽象,什么都想用设计模式封装一下抽象一下。这就很容易导致一些过度设计/过度抽象导致游戏性能太差。
面向数据思想,尽量减少虚函数的使用,多利用数据组合成对象,而不是重写各种基类虚函数。对于一些数据结构的考量,也尽量偏多使用连续存储的结构(例如数组)。



