最好放下架子,写一点难看但更快速的代码:
void Vector::Add(const Vector &;v1,const Vector &;v2)
注意+=操作符并没有同样的
问题,它只是修改第一个参数,并不需要返回一个临时对象,所以,可能的情况下,你也可以用+=代替+。
1.3 使用轻量级的构造函数
在上一个例子中Vector的构造函数是否需要初始化它的元素为0?这个问题可能在你的代码中会有好几处出现。如果是的话,它使得无论是否必要,所有的调用都要付初始化的代价。典型的来说,临时顶点以及成员变量就会要无辜的承受这些额外的开销。
一个好的编译器可以很好的移除一些这种多余的代码,但是为什么要冒这个险呢?作为一般的规则,你希望构造函数初始化所有的成员变量,因为未初始化的数据将产生错误。但是,在频繁实例化的小类中,特别是一些临时对象,你应该准备向效率规则妥协。首选的情况就是在许多游戏中有的vector和Matrix 类,这些类显然应当提供一些方法置0和识别,但它的缺省构造函数却应当是空的。
这个概念的推论就是你应当为这种类提供另一个构造函数。如果我们的第二个例子中的Vebicle类是这样写的话:
class Vehicle
{
public:
vehicle()
{
}
void SetName(const std::string &;name)
{
mName=name;
}
private:
std::string mName
};
我们省去了构造mName的开销,而在稍后用SetName方法设置了其值。相似的,使用拷贝构造函数将比构造一个对象然后用=操作符要好一些。宁愿这样来构造:Vebicle V1(V2)也不要这样来构造:
Vehicle v1;v1=v2;
如果你需要阻止编译器帮你拷贝对象,把拷贝构造函数和操作符=声明为私有的,但不要实现其中任何一个。这样,任何企图对该对象的拷贝都将产生一个编译时错误。最好也养成定义单参数构造函数的习惯,除非你是要做类型转换。这样可以防止编译器在做类型转换时产生的隐藏的临时对象。
1.4 预分配和Cache对象
一个游戏一般会有一些类会频繁的分配和释放,比如武器什么的。在C程序中,你会分配一个大数组然后在需要的时候使用。在C++中,经过小小的规划以后,你也可以这样干。这个方法是不要一直构造和析构对象而是请求一个新而把旧的返回给Cache。Cache可以实现成一个模板,它就可以为所有的有一个缺省构造函数的类工作。Cache模板的Sample可以在附带的CD中找到。
你也可以在需要时分配一些对象来填充Cache,或者预先分配好。如果你还要对这些对象维护一个堆栈的话(表示在你删除对象X之前,你先要删除所有在X后面分配的对象),你可以把Cache分配在一个连续的内存块中。
2、内存管理
C++应用
程序一般要比C程序更深入到内存管理的细节。在C中,所有的分配都简单的通过malloc和free来进行,而C++则还可以通过构造临时对象和成员变量来隐式的分配内存。很多C++游戏程序需要自己的内存管理程序。 由于C++游戏程序要执行很多的分配,所以要特别小心堆的碎片。一个方法是选择一条复杂的路:要么在游戏开始后根本不分配任何内存,要么维护一个巨大的连续内存块,并按期释放(比如在关卡之间)。在现代机器上,如果你想对你的内存使用很警惕的话,很严格的规则是没必要的。
第一步是重载new和 Delete操作符,使用自己实现的操作符来把游戏最经常的内存分配从malloc定向到预先分配好的内存块去,例如,你发现你任何时候最多有10000 个4字节的内存分配,你可以先分配好40000字节,然后在需要时引用出来。为了跟踪哪些块是空的,可以维护一个由每一个空的块指向下一个空的块的列表 free list。在分配的时候,把前面的block移掉,在释放的时候,把这个空块再放到前面去。图1描述了这个free list如何在一个连续的内存块中,与一系列的分配和
释放协作的