让我们分解它是如何工作的:
这是函数声明。重要的一点是P
与 无关T
。所以,我们需要多加注意,因为可能会发生一些不好的事情......
template <typename P>
P* Write(P* pData)
{
让我们表示 指向的内存pData
。为了解释起见,我假设它pData
指向内存中足够大的区域(否则Write
可能会导致段错误),而这Vector3<T>
只是 3 T
s 的大小。在下文中,我将采用T = float
, with sizeof(float) = 4
,但我的解释仍然适用于其他类型。所以,在这里sizeof(Vector3<float>) = 12
。
所以,这里是内存,|-|
是一个字节:
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
^
|
pData : P*
我们将其解释pData
为指向 a 的指针Vector3<T>
。一般来说,这是一种糟糕的风格,P
任何东西都可以。
Vector3<T>* pVector = (Vector3<T>*) pData;
以下行:
*pVector++ = *this;
可分为:
*pVector = *this;
pVector++;
*pVector = *this;
将向量 ( *this
) 的内容分配给数据。现在的记忆是:
pVector : Vector3<float>*
|
v
|X|X|X|X|Y|Y|Y|Y|Z|Z|Z|Z|-|-|-|-|-|
^^^^^^^^^^^^^^^^^^^^^^^^^
copied content of the vector
pVector++;
将向量递增1 * sizeof(Vector3<float>)
,因此它现在指向尚未写入的内存:
pVector : Vector3<float>*
|
v
|X|X|X|X|Y|Y|Y|Y|Z|Z|Z|Z|-|-|-|-|-|
pVector
被抛回P*
并返回。
return (P*) pVector;
}
这可以链接写入,因为对返回指针的写入不会覆盖第一次写入:
char data[2 * sizeof(Vector3<float>)];
v2.Write(v1.Write(data));
// now data is:
// contents of v2
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// |X1|X1|X1|X1|Y1|Y1|Y1|Y1|Z1|Z1|Z1|Z1|X2|X2|X2|X2|Y2|Y2|Y2|Y2|Z2|Z2|Z2|Z2
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// contents of v1
现在,一些缺陷:
这个函数对io很有用,相当于memcpy
向量上的a。但是,它有很多缺陷,主要是模板参数。直接将此类操作写入内存应该使用char
,而不是其他东西。在内存中写入以覆盖类的内容(即,不是 io)是一种非常糟糕的做法,(赋值运算符是为这样的任务而设计的,它们更安全)。此外,以下示例不是自描述的:
vec.Write<MyTimerClass>(pointer); // Does not make sense if you are reading this line for the first time
其次,内存复制存在问题。在这里,在解释中,我假设Vector3
是一个简单的类型。并非总是如此。如果它有不同大小的虚函数和成员,内存中的布局将是实现定义的,例如:
X, Y, Z padding
------------------------ ----
|P|P|P|P|X|X|X|X|Y|Y|Y|Y|Z|Z|Z|Z|a|a|.|.
-------- ----
vtable some
pointer other
member
这对 io 来说是不可靠的。请参阅此 SO 问题(接受的答案是:“这是msvc的工作原理”...)。而对于多重虚拟继承,它变成了一场真正的噩梦。
顺便说一句,这种方法的安全实现类似于:
template<typename IOBuffer>
bool Write(IOBuffer& buffer)
{
return buffer << X << Y << Z;
}
或更好:
virtual bool Write(IOBufferInterface& buffer)
{
return buffer << X << Y << Z;
}
它更易于理解、维护、调试等......