【问题标题】:What is this function template in this class template doing?这个类模板中的这个函数模板是做什么的?
【发布时间】:2013-04-06 15:12:39
【问题描述】:

我正在阅读我正在阅读的书中的 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 的指针视为可以轻松地从指向 Vector3 的指针转换为类名。怎么样?如果我有一个Vector3&lt;float&gt; someVector,是什么让指向这个someVector 的指针变成指向float 的指针? (甚至int?)它也做相反的事情:给函数一个浮点数组,然后它可以将它转换成一个Vector3的数组。

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

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

【问题讨论】:

  • 它将 Vector3 的内容存储在其他位置,由 any 类型的指针指向。可怕! ++ 增加指针以指向存储的数据。
  • 我还是不明白选角。是不是因为 Vector3 只是一组 floats 或其他什么,它可以被视为相同类型的数组?这将假设结构的成员像数组一样在内存中对齐,除非我不理解从 Vector3 到数组的这种转换。

标签: c++


【解决方案1】:

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

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

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

    让我们表示pData指向的内存。为了便于解释,我假设pData 指向内存中足够大的区域(否则Write 可能会导致段错误),并且Vector3&lt;T&gt; 的大小仅为3 @987654329 @s。在下文中,我将使用T = floatsizeof(float) = 4,但我的解释仍然适用于其他类型。所以,这里是sizeof(Vector3&lt;float&gt;) = 12

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

    |-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
    ^
    |
    pData : P*
    
  2. 我们将pData 解释为指向Vector3&lt;T&gt; 的指针。这是不好的风格一般,因为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&lt;float&gt;),因此它现在指向尚未写入的内存:

                              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。但是,它有很多缺陷,主要是模板参数。为此类操作直接写入内存应使用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 来说是不可靠的。请参阅this SO question(接受的答案是:“这是 msvc 的工作原理”...)。而对于多重虚拟继承,这将成为一场真正的噩梦。

顺便说一句,这种方法的安全实现应该类似于

template<typename IOBuffer>
bool Write(IOBuffer& buffer)
{
    return buffer << X << Y << Z;
}

或更好:

virtual bool Write(IOBufferInterface& buffer)
{
    return buffer << X << Y << Z;
}

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

【讨论】:

  • 非常彻底,我很感激。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-02
  • 1970-01-01
  • 2015-03-22
  • 1970-01-01
  • 1970-01-01
  • 2014-06-14
  • 1970-01-01
相关资源
最近更新 更多