Vector クラスの実装を行うたびに共通化できそうな処理を何度も書いていると感じませんか?
この記事では繰り返しコードをなるべく減らした実装の紹介です。
共通化に伴い、処理速度や使用メモリなどの最適化は完全に無視しています。
クラス図
基底クラスの実装 (IVector)
純粋仮想関数は使用禁止です。
使用するとクラスサイズが 8 byte 増加します。
IVector.h
#include <cmath>
#include <cstdint>
#include <string>
#include <ostream>
template<typename CLASS, typename TYPE>
class IVector
{
public:
inline IVector() noexcept = default;
inline ~IVector() noexcept = default;
inline constexpr IVector(const TYPE& InValue) noexcept
{
(*this) = InValue;
}
inline constexpr IVector(const IVector& InVector) noexcept
{
(*this) = InVector;
}
};
要素数が不明なため、テンプレート引数から算出する必要があります。
IVector.h
/* メンバ関数の実装 */
public:
/* 要素数の取得 */
static constexpr inline std::uint32_t GetNumElements() noexcept
{
return static_cast<std::uint32_t>(sizeof(CLASS)) / static_cast<std::uint32_t>(sizeof(TYPE));
}
要素へのアクセスは基本的にポインタからとなります。
そのため、ほぼすべての関数内でループ処理が行われます。
また、代入演算子のみ具体的な処理を行っています。
他演算子は、オーバーロードした演算子の組み合わせで実装できるため簡略化が可能です。
IVector.h
/* 演算子オーバーロード */
public:
constexpr IVector& operator=(const IVector& InVector) noexcept
{
const auto* TempPointer = reinterpret_cast<const TYPE*>(&InVector);
auto* ThisPointer = reinterpret_cast<TYPE*>(this);
for (std::uint32_t i = 0U; i < GetNumElements(); ++i)
{
ThisPointer[i] = TempPointer[i];
}
return (*this);
}
constexpr IVector& operator=(const TYPE& InValue) noexcept
{
auto* ThisPointer = reinterpret_cast<TYPE*>(this);
for (std::uint32_t i = 0U; i < GetNumElements(); ++i)
{
ThisPointer[i] = InValue;
}
return (*this);
}
inline constexpr IVector& operator+=(const IVector& InVector) noexcept
{
return (*this) = (*this) + InVector;
}
inline constexpr IVector& operator+=(const TYPE& InValue) noexcept
{
return (*this) += IVector(InValue);
}
inline constexpr IVector& operator-=(const IVector& InVector) noexcept
{
return (*this) = (*this) - InVector;
}
inline constexpr IVector& operator-=(const TYPE& InValue) noexcept
{
return (*this) -= IVector(InValue);
}
inline constexpr IVector& operator*=(const IVector& InVector) noexcept
{
return (*this) = (*this) * InVector;
}
inline constexpr IVector& operator*=(const TYPE& InValue) noexcept
{
return (*this) *= IVector(InValue);
}
inline constexpr IVector& operator/=(const IVector& InVector) noexcept
{
return (*this) = (*this) / InVector;
}
inline constexpr IVector& operator/=(const TYPE& InValue) noexcept
{
return (*this) /= IVector(InValue);
}
同じクラス同士の演算とすることで、
1つの演算子につき具体的な処理を行うオーバーロードが1つで十分になります。
IVector.h
template<typename CLASS, typename TYPE>
constexpr CLASS operator+(const IVector<CLASS, TYPE>& InVector_A, const IVector<CLASS, TYPE>& InVector_B) noexcept
{
const auto* TempPointer_A = reinterpret_cast<const TYPE*>(&InVector_A);
const auto* TempPointer_B = reinterpret_cast<const TYPE*>(&InVector_B);
CLASS TempVector;
auto TempPointer = reinterpret_cast<TYPE*>(&TempVector);
for (std::uint32_t i = 0U; i < IVector<CLASS, TYPE>::GetNumElements(); ++i)
{
TempPointer[i] = TempPointer_A + TempPointer_B;
}
return TempVector;
}
template<typename CLASS, typename TYPE>
inline constexpr CLASS operator+(const IVector<CLASS, TYPE>& InVector, const TYPE& InValue) noexcept
{
return InVector + IVector<CLASS, TYPE>(InValue);
}
template<typename CLASS, typename TYPE>
constexpr CLASS operator-(const IVector<CLASS, TYPE>& InVector_A, const IVector<CLASS, TYPE>& InVector_B) noexcept
{
const auto* TempPointer_A = reinterpret_cast<const TYPE*>(&InVector_A);
const auto* TempPointer_B = reinterpret_cast<const TYPE*>(&InVector_B);
CLASS TempVector;
auto TempPointer = reinterpret_cast<TYPE*>(&TempVector);
for (std::uint32_t i = 0U; i < IVector<CLASS, TYPE>::GetNumElements(); ++i)
{
TempPointer[i] = TempPointer_A - TempPointer_B;
}
return TempVector;
}
template<typename CLASS, typename TYPE>
inline constexpr CLASS operator-(const IVector<CLASS, TYPE>& InVector, const TYPE& InValue) noexcept
{
return InVector - IVector<CLASS, TYPE>(InValue);
}
template<typename CLASS, typename TYPE>
constexpr CLASS operator*(const IVector<CLASS, TYPE>& InVector_A, const IVector<CLASS, TYPE>& InVector_B) noexcept
{
const auto* TempPointer_A = reinterpret_cast<const TYPE*>(&InVector_A);
const auto* TempPointer_B = reinterpret_cast<const TYPE*>(&InVector_B);
CLASS TempVector;
auto TempPointer = reinterpret_cast<TYPE*>(&TempVector);
for (std::uint32_t i = 0U; i < IVector<CLASS, TYPE>::GetNumElements(); ++i)
{
TempPointer[i] = TempPointer_A * TempPointer_B;
}
return TempVector;
}
template<typename CLASS, typename TYPE>
inline constexpr CLASS operator*(const IVector<CLASS, TYPE>& InVector, const TYPE& InValue) noexcept
{
return InVector * IVector<CLASS, TYPE>(InValue);
}
template<typename CLASS, typename TYPE>
constexpr CLASS operator/(const IVector<CLASS, TYPE>& InVector_A, const IVector<CLASS, TYPE>& InVector_B) noexcept
{
const auto* TempPointer_A = reinterpret_cast<const TYPE*>(&InVector_A);
const auto* TempPointer_B = reinterpret_cast<const TYPE*>(&InVector_B);
CLASS TempVector;
auto TempPointer = reinterpret_cast<TYPE*>(&TempVector);
for (std::uint32_t i = 0U; i < IVector<CLASS, TYPE>::GetNumElements(); ++i)
{
TempPointer[i] = TempPointer_A / TempPointer_B;
}
return TempVector;
}
template<typename CLASS, typename TYPE>
inline constexpr CLASS operator/(const IVector<CLASS, TYPE>& InVector, const TYPE& InValue) noexcept
{
return InVector / IVector<CLASS, TYPE>(InValue);
}
各次元で呼び出せる関数を実装します。
特定の次元のみ処理が違う場合などはここでは実装しません。
IVector.h
template<typename CLASS, typename TYPE>
constexpr TYPE InnerProduct(const IVector<CLASS, TYPE>& InVector_A, const IVector<CLASS, TYPE>& InVector_B) noexcept
{
const auto* TempPointer_A = reinterpret_cast<const TYPE*>(&InVector_A);
const auto* TempPointer_B = reinterpret_cast<const TYPE*>(&InVector_B);
TYPE TempValue = static_cast<TYPE>(0);
for (std::uint32_t i = 0U; i < IVector<CLASS, TYPE>::GetNumElements(); ++i)
{
TempValue += TempPointer_A[i] * TempPointer_B[i];
}
return TempValue;
}
template<typename CLASS, typename TYPE>
inline constexpr TYPE LengthSquared(const IVector<CLASS, TYPE>& InVector) noexcept
{
return InnerProduct(InVector, InVector);
}
template<typename CLASS, typename TYPE>
inline constexpr TYPE Length(const IVector<CLASS, TYPE>& InVector)
{
return static_cast<TYPE>(std::sqrt(LengthSquared(InVector)));
}
template<typename CLASS, typename TYPE>
inline constexpr TYPE Distance(const IVector<CLASS, TYPE>& InVector_A, const IVector<CLASS, TYPE>& InVector_B)
{
return Length((InVector_B - InVector_A));
}
template<typename CLASS, typename TYPE>
inline constexpr CLASS Normalize(const IVector<CLASS, TYPE>& InVector)
{
return InVector / Length(InVector);
}
template<typename CLASS, typename TYPE>
inline constexpr CLASS Reflect(const IVector<CLASS, TYPE>& InVector, const IVector<CLASS, TYPE>& InNormal) noexcept
{
return InVector + (static_cast<TYPE>(-2) * InnerProduct(InVector, InNormal)) * InNormal;
}
派生クラス (TVector2)
コンストラクタとメンバ変数のみの実装で十分となります。
Vector2.h
#include "IVector.h"
#define VECTOR2_NUM_ELEMENS (2U)
template<typename T>
class TVector2 : public IVector<TVector2<T>, T>
{
public:
inline TVector2() noexcept = default;
inline ~TVector2() noexcept = default;
inline constexpr TVector2(const T& InX, const T& InY) noexcept
: X(InX)
, Y(InY) {}
inline constexpr TVector2(const T& InValue) noexcept
: IVector<TVector2<T>, T>(InValue) {}
inline constexpr TVector2(const TVector2& InVector) noexcept
: IVector<TVector2<T>, T>(InVector) {}
public:
union
{
struct
{
T X;
T Y;
};
T Pointer[VECTOR2_NUM_ELEMENS];
};
};
固有の処理を実装します。
Vector2.h
template<typename T>
constexpr T OuterProduct(const TVector2<T>&InVector_A, const TVector2<T>&InVector_B) noexcept
{
/* [NOTE] scalar quantity (magnitude of outer product) */
return (InVector_A.X * InVector_B.Y) - (InVector_A.Y * InVector_B.X);
}
派生クラスの実装 (TVector3)
コンストラクタとメンバ変数のみの実装で十分となります。
Vector3.h
#include "IVector.h"
#define VECTOR3_NUM_ELEMENS (3U)
template<typename T>
class TVector3 : public IVector<TVector3<T>, T>
{
public:
inline TVector3() noexcept = default;
inline ~TVector3() noexcept = default;
inline constexpr TVector3(const T& InX, const T& InY, const T& InZ) noexcept
: X(InX)
, Y(InY)
, Z(InZ) {}
inline constexpr TVector3(const T& InValue) noexcept
: IVector<TVector3<T>, T>(InValue) {}
inline constexpr TVector3(const TVector3& InVector) noexcept
: IVector<TVector3<T>, T>(InVector) {}
public:
union
{
struct
{
T X;
T Y;
T Z;
};
T Pointer[VECTOR3_NUM_ELEMENS];
};
};
固有の処理を実装します。
Vector3.h
template<typename T>
constexpr TVector3<T> OuterProduct(const TVector3<T>& InVector_A, const TVector3<T>& InVector_B) noexcept
{
const auto VectorNumElements = TVector3<T>::GetNumElements();
const auto* TempPointer_A = InVector_A.Pointer;
const auto* TempPointer_B = InVector_B.Pointer;
TVector3<T> TempVector;
for (std::uint32_t i = 0U; i < VectorNumElements; ++i)
{
const auto ArrayIndex_A = (i + 1U) % VectorNumElements;
const auto ArrayIndex_B = ((VectorNumElements - 1U) + i) % VectorNumElements;
TempVector.v[i] = TempPointer_A[ArrayIndex_A] * TempPointer_B[ArrayIndex_B] - TempPointer_A[ArrayIndex_B] * TempPointer_B[ArrayIndex_A];
}
return TempVector;
}
まとめ
共通化することでコードの読みやすさや機能の追加が容易になるなどのメリットはあります。
しかし、無理に共通化する必要はありません。
自分がわかりやすいコードを書ければ問題なしです。