【问题标题】:Writing a generalized Vector Class in C++用 C++ 编写一个通用的向量类
【发布时间】:2014-01-28 18:12:34
【问题描述】:

我正在尝试实现一个 Vector 类以在我的图形项目中使用。我想按长度和类型对向量进行模板化,并且我希望能够使用 A.x、A.y、A.z、A.w 来访问向量 A 的成员(例如)。

这是我的第一次尝试。我绝对不是 C++ 模板方面的专家!我试图用 Vec2、Vec3 和 Vec4 的专用版本实现一个通用 Vector 类。每个专业类都有一个联合,允许我使用它们的名称访问坐标。不幸的是,如果不为每个专用类重新实现每个向量函数,我无法弄清楚如何做到这一点。

请记住,我想实现一些仅适用于特定长度向量的函数。例如,叉积仅适用于 vec3,而点积(或运算符*)适用于所有长度的向量。

#include <cstdint>

using namespace std;

//*********************************************************************
// Vector implementation parameterized by type and size.
//*********************************************************************

template <typename T, size_t SIZE> 
class Vector {

    public:
        T data[SIZE];
        size_t size;

        Vector(T* arr);
};

template <typename T, size_t SIZE> Vector<T, SIZE>::Vector(T* arr) {
    size = SIZE;

    for(int i=0; i<size; i++) {
        data[i] = arr[i];
    }

}

//*********************************************************************
// Vec2 is a specialization of Vector with length 2.
//*********************************************************************

typedef Vector<float, 2> Vec2f;
typedef Vector<int, 2> Vec2d;

template <typename T>
class Vector <T, 2> {

    public:
        union {
            T data[2];
            struct {
                T x;
                T y;
            };
        };

        size_t size;

        Vector(T x, T y);
};

template <typename T> Vector<T, 2>::Vector(T x, T y) {
    data[0] = x; data[1] = y;
    size = 2;
}

//*********************************************************************
// Vec3 is a specialization of Vector with length 3.
//*********************************************************************

typedef Vector<float, 3> Vec3f;
typedef Vector<int, 3> Vec3d;

template <typename T>
class Vector <T, 3> {

    public:
        union {
            T data[3];
            struct {
                T x;
                T y;
                T z;
            };
        };

        size_t size;

        Vector(T x, T y, T z);
};

template <typename T> Vector<T, 3>::Vector(T x, T y, T z) {
    data[0] = x; data[1] = y; data[2] = z;
    size = 3;
}

//*********************************************************************
// Vec4 is a specialization of Vector with length 4.
//*********************************************************************

typedef Vector<float, 4> Vec4f;
typedef Vector<int, 4> Vec4d;

template <typename T>
class Vector <T, 4> {

    public:
        union {
            T data[4];
            struct {
                T x;
                T y;
                T z;
                T w;
            };
        };

        size_t size;

        Vector(T x, T y, T z, T w);
};

template <typename T> Vector<T, 4>::Vector(T x, T y, T z, T w) {
    data[0] = x; data[1] = y; data[2] = z; data[3] = w;
    size = 4;
}

【问题讨论】:

  • 这不是继承可以解决的问题吗? Vector2 将是基础。让vector3继承vector2的成员并添加到它上面。 Vector4 继承 vector3 并添加到它。我能看到 atm 的唯一开销是虚拟通话。
  • 您应该为自己节省大量时间并使用std::vector。它是免费的,已经实施,并且已经过测试
  • @ThomasMatthews OP 正在尝试实现数学向量,而不是容器。
  • 您当前对联合的使用肯定会引入未定义的行为。
  • 我记得联合中的匿名结构不是标准的?

标签: c++ templates vector


【解决方案1】:

避免在多个专业化中重复实现相同功能的通常解决方法是从一个公共基类继承,并在基类中实现这些功能:

template <typename T>
struct VectorBase {
  // common stuff
};

template <typename T, std::size_t N>
struct Vector : VectorBase<T> {
  // ...
};

template <typename T>
struct Vector<T, 2> : VectorBase<T> {
  // ...
};

template <typename T>
struct Vector<T, 3> : VectorBase<T> {
  // ...
  friend Vector<T, 3> cross(const Vector<T, 3>&, const Vector<T, 3>&);
};

您将遇到的下一个问题是需要从公共基访问派生类中的成员(例如,获取 xsize() 的值)。你可以使用Curiously Recurring Template Pattern (CRTP)

template <typename T, typename CRTP>
struct VectorBase {
  CRTP& crtp() { return static_cast<CRTP&>(*this); }
  const CRTP& crtp() const { return static_cast<const CRTP&>(*this); }

  std::size_t size() const {
    return std::extent<decltype(CRTP::data)>::value;
  }

  void zero() {
    std::fill(std::begin(crtp().data), std::end(crtp().data), T());
  }

  using iterator = T*;
  using const_iterator = const T*;
  iterator begin() { return &crtp().data[0]; }
  iterator end() { return &crtp().data[0] + size(); }
  const_iterator begin() const { return &crtp().data[0]; }
  const_iterator end() const { return &crtp().data[0] + size(); }

  T& operator [] (std::size_t i) {
    return crtp().data[i];
  }
  const T& operator [] (std::size_t i) const {
    return crtp().data[i];
  }
};

template <typename T, std::size_t N>
struct Vector : VectorBase<T, Vector<T, N>> {
  union {
    T data[N];
    struct {
      T x, y, z, w;
    };
  };
};

template <typename T>
struct Vector<T, 2> : VectorBase<T, Vector<T, 2>> {
  union {
    T data[2];
    struct {
      T x, y;
    };
  };
};

template <typename T>
struct Vector<T, 3> : VectorBase<T, Vector<T, 3>> {
  union {
    T data[3];
    struct {
      T x, y, z;
    };
  };
};

template <typename T, typename U, std::size_t N>
auto operator * (const Vector<T, N>& a, const Vector<U, N>& b)
 -> Vector<decltype(a[0] * b[0]), N> {
  Vector<decltype(a[0] * b[0]), N> result;
  for (std::size_t i = 0; i < N; ++i) {
    result[i] = a[i] * b[i];
  }
  return result;
}

template <typename T, typename U, std::size_t N>
auto dot(const Vector<T, N>& a, const Vector<U, N>& b)
 -> decltype(a[0] * b[0]) {
  auto product = a * b;
  using V = decltype(product.x);
  return std::accumulate(std::begin(product), std::end(product), V(0));
}

** Sample Code at Coliru **

评论者提到的未定义行为有两个问题:

  1. 匿名结构(例如,struct { T x, y, z; };)是 GNU 扩展,因此可能仅适用于 GCC 和兼容的编译器 (clang)。

  2. 从除最后存储的成员之外的联合成员中读取通常是未定义的行为;鉴于所涉及的每种类型都是标准布局并且所有读取/写入的值都是相同类型,这个特定示例至少是临界的。我会让其他人来做确切的语言律师,并简单地说明当使用支持匿名结构扩展的最新编译器时,代码几乎肯定会按预期执行。

如果这些非标准要求中的任何一个让您感到困扰,请删除结构和联合,以便数组是唯一的数据成员。然后为符号名称添加函数,例如T&amp; x() { return data[0]; },只是稍微麻烦一点。

【讨论】:

  • 嘿!谢谢您的帮助!我肯定还在学习。这是有道理的,但我仍然不确定如何以 vec.x 访问 vec.data[0] 我应该放弃它并使用 vec.data[0] 来处理所有事情吗?
  • 哦等等。我想你建议我删除数据数组,只在基本情况下有一个字段 x,在长度为 2 的情况下有一个字段 y,在长度为 3 的情况下有一个字段 z,在长度为 4 的情况下有一个 w。这对我有用,但我很好奇是否有办法做到这一切,并且仍然有一个由数据数组支持的任意长度的向量。这有意义吗?
  • @user2921464 我并不是在暗示,我只是从结构中省略了与 OP 中相同的细节。我试图专注于使用通用基类 + CRTP 技术,但显然为了清晰而丢失了太多细节。我将把 Coliru 上的大部分代码提取出来,并在此处将其全部内联,并为操作员演示添加一个点积实现。
  • 我想我开始明白了。感谢您非常详细的解释!
  • @user2921464 第 18 行是别名声明 (using iterator = T*;) 的第一次出现。 g++ 4.7 首次支持类型别名,所以我猜你使用的是早期版本。 (Deja vu, just answered this an hour ago.) 将它们替换为等效的 typedef typedef T* iterator; typedef const T* const_iterator...typedef decltype(product.x) V;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-22
  • 1970-01-01
  • 2012-04-02
相关资源
最近更新 更多