Game Coding Complete 4th ed. by Mike McShaffry and David Graham
(67-68)说类应该使用流来初始化对象:
class AnimationPath
{
public:
AnimationPath();
Initialize(std::vector<AnimationPathPoint> const & srcPath);
Initialize(InputStream & stream);
//of course lots more code follows.
};
这个类有一个默认的构造函数和两种初始化它的方法。第一个是通过经典参数列表,在本例中是
AnimationPathPoint
的列表。第二个通过流对象初始化类。这很酷,因为您可以从磁盘,内存流甚至网络初始化对象....(包含引用的章节的作者Graham先生,继续解释为什么使用流作为构造函数的参数是坏的。流可能会失败并且您的对象处于失败状态。
我的问题是,不是关于使用此策略,而是为什么不使用流操作符operator<<
和operator>>
来代替Initialize
方法或除Initialize
方法之外。 ({{1}}方法甚至可以只是流操作符的代理。)
差异是语义还是有合理的理由使用一个而不是另一个?
答案 0 :(得分:3)
operator<<
和operator>>
是可憎的,也是C ++ IOStreams库中较大的错误之一。永远不要复制那种模式明确std::istream&
(或你的框架的等价物)更有意义。
更简洁的方法(尽管并不总是最可行)是拥有一个完全独立的AnimationPathSerializer
来处理映射到AnimationPath
个对象的流。保持每个类型/对象小,只关注一个问题。动画不需要知道如何将自身加载或保存为动画,并且有时可能需要支持具有非常不同语义的几种完全不同的序列化格式(因此单个通用流类型或单个序列化对象中的接口没有意义。)
答案 1 :(得分:0)
如果您将流传递给初始化例程,则该流可能已经定义了>>
<<
个运算符,您可以使用它们在您的类中设置成员变量。
如果您定义自己的运算符,它将无法清楚地知道发生了什么,因为您可以在这些运算符中编写您想要的任何代码。如果有人看到使用流初始化,那么立即假设构造函数(或init函数)正在进行某种序列化是合理的。这使代码可读。
虽然你没有 使用这样的流,但这是一个很好的入门方式。因此,如果该书实际上说你应该使用流,那么我不得不反对并说你可以使用流,而不是应该。
答案 2 :(得分:0)
他们在谈论的是序列化。在游戏中,您通常需要大量数据才能使您的工作方式发挥作用。如何实现序列化真的非常适合你。使用std :: stream是一个好主意,因为它可以指向他们提到的任何内容(文件,字符串,网络......)但是给每个对象管理他们自己的序列化数据的权限......不太确定!
因此,最重要的部分不是<<
和>>
运算符,而是您希望序列化程序如何向前和向后兼容。这是大多数使用这种机制的人的噩梦(甚至MFC也有类似的功能!)。
因此,如果您使用二进制输入文件,则需要工具来验证文件是否有效。当你想要添加一个字段时,所有旧文件都是kaput!除非你花费无数个小时来支持旧格式,否则你会使用真正可扩展的格式。
因此,更好的方法是使用高级加载函数可以处理的格式,并且可以轻松扩展,而无需在每次添加字段时进行任何工作。如果你真的想使用二进制格式,我会建议一个标记文件,有点像IFF你有一个结构,如:
32 bits -- tag name
32 bits -- size
size bytes -- data
Flash使用类似的格式,&#34;压缩&#34;标签和大小为16位(标签为10位,大小为6位),除非大小太大,否则它们使用另外32位(在这种情况下,大小的6位设置为0x3F)。当然,他们有一个知道如何读取该头的加载,但是将数据块传递给必须解密该二进制块的对象(虽然可以是一个简单的结构,但请记住,计算机可能是小的或大的结尾...)。
当您需要添加数据时,您可以增加数据&#39;结构体。很容易,在负载时你必须确保你有正确的尺寸&#39;并相应地调整结构的参数。如果您有大块数据,还可以考虑压缩它们。也许压缩版本的标签名称会更改,或者为此目的在那里保留一个标志。
另一种非常适用于游戏的格式是XML。许多人不喜欢它,因为解析很重(如&#34;慢&#34;)但它确实是可扩展的。
在这两种情况下,如果只是将流传递给每个对象,就会遇到大问题。我想你的对象是在树中组织的,所以XML是完全匹配的。 (只有在加载过程完成后才能解析链接[在HREF中找到的ID / IDREF或XPath]。)
如果您开始使用流,那么当您调用Initialize()函数时,流在哪里?你认为它只是在正确的地方,神奇地?如果是这样,那么在该对象之前的任何地方添加一个字段最终会破坏其初始化。
所以我认为它应该做的方式是一个知道工厂创建对象的加载器,并传递他们的DomElement与从文件,内存缓冲区,网络加载的数据...然后一个非常简单的加载函数可以处理所有I / O问题,每个对象只负责在使用它之前检查它们的DomElement是否有效。
就个人而言,我最终会使用ostream& operator << (ostream& out)
运算符进行调试,打印出对象并查看是否存在错误。这很酷,因为你可以做到:
cout << myObj;
类似于使用.toString()函数在Java之类的语言中所做的。
因此,如果您重载了<<
运算符以序列化您的对象,特别是如果它序列化为二进制,则该功能不可用。
最后一句话:一般来说,使用函数比使用运算符更有说服力,尤其是做不自动的事情,这是另一个C ++程序员所期望的。在这种情况下,我会说<<
和>>
重载符合C ++流的通常用法,因此它是你提出的有效点,我看不出任何区别使用Initialize()和(我想象)一个Save()函数,或<<
和>>
。但是,由于我之前提到的所有考虑因素,我认为你的情况并不保证以这种方式使用流。