1

我正在阅读我正在阅读的书中的 C++ 类模板,虽然大部分内容都很清楚,但这个特殊的功能真的让我很烦:

template <typename T> 
struct Vector3 {

T x; T y; T z;

//... several methods, constructors, etc

//Then this one, which is really confusing me:

template <typename P> 
P* Write(P* pData)
   {
    Vector3<T>* pVector = (Vector3<T>*) pData; 
    *pVector++ = *this;
    return (P*) pVector;
   }

首先,该函数似乎将 P 的数组或指向 P 的指针视为可以轻松地从指向 a 的指针转换为Vector3类名。怎么样?如果我有一个Vector3<float> someVector,是什么让一个指向这个someVectorcastable 的指针变成一个指向的指针float?(甚至int?)它也做相反的事情:为函数提供一个浮点数组,然后它可以将其转换为Vector3.

所以这是我困惑的第一个领域。

接下来是*pVector++ = *this;-- 我认为这是这里的指针算术,如果pVector指向数组中的第二个元素,使用这个表达式我们将指针递增到数组的下一个元素,但只有我们首先分配*this给指向的当前元素pVector。假设我在这方面是正确的,并不pVector总是指向数组中的第一个元素,因为它只是在前一行创建的?!++在这种情况下,运营商的目的是什么?

4

1 回答 1

5

让我们分解它是如何工作的:

  1. 这是函数声明。重要的一点是P与 无关T。所以,我们需要多加注意,因为可能会发生一些不好的事情......

    template <typename P> 
    P* Write(P* pData)
    {
    

    让我们表示 指向的内存pData。为了解释起见,我假设它pData指向内存中足够大的区域(否则Write可能会导致段错误),而这Vector3<T>只是 3 Ts 的大小。在下文中,我将采用T = float, with sizeof(float) = 4,但我的解释仍然适用于其他类型。所以,在这里sizeof(Vector3<float>) = 12

    所以,这里是内存,|-|是一个字节:

    |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
    ^
    |
    pData : P*
    
  2. 我们将其解释pData为指向 a 的指针Vector3<T>一般来说,这是一种糟糕的风格,P任何东西都可以。

        Vector3<T>* pVector = (Vector3<T>*) pData; 
    
  3. 以下行:

        *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|-|-|-|-|-|
      
  4. 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;
}

它更易于理解、维护、调试等......

于 2013-04-14T14:07:08.043 回答