利用C++ template,請問我該如何設計這個向量類(Vector)?

最近在重構自己寫的一個簡單的線性代數庫,主要是給自己寫的一個軟渲染器用的,

遇到了一個設計上的問題。

比如一個N維的向量類Vector,可以這樣來寫:

template&
struct Vector
{
T a[N];
};

我希望當N = 4時,它能特化,並能特化出自己的名字來,實際效果就像這樣:

template&
struct Vector
{
union
{
struct
{
T x, y, z, w;
};
T a[4];
};
};

因為圖形學一般只用到四維以下的向量,且一般的命名習慣是:

四維:(x, y, z, w)

三維:(x, y, z)

二維:(x, y)

如果用上述N維向量模板的話,就只能用諸如a[0], a[1], a[2], a[3]的形式來訪問了,感覺代碼可讀性會下降。所以我才想有沒有什麼辦法能讓成員名字特化,比如讓a[0]有個別名叫x,懇請各位前輩指教!謝謝!

PS:

我現在的寫法是Vector2D,Vector3D,Vector4D分別手動編寫,形成3個不同的類,感覺代碼量膨脹得很大,不利於維護。

補充說明一下,我希望實現的最終效果是:

1. 用N維向量模板實現一些通用操作,比如求兩個向量的點積,求某個向量的長,將某個向量單 位化等

2. 當且僅當N = 2,3,4時,使它們的成員能有特殊的名字,
使得向量的第1個元素叫做x, 第2個元素 叫做y, 第3個元素叫做z, 第4個元素叫做w

3. 希望實現這個效果只是為了2個目的:

①利用模板減少代碼量,提升代碼的維護性

②利用別名,使得代碼長得更像現實世界中的數學公式,提升可讀性

③如果①②發生衝突,我傾向於捨棄①,即我之前的做法(Vector2D, Vector3D, Vector4D)


不要把簡單問題複雜化。

不要把 N 放到模板參數里。一共就仨還用模板,不是強行把問題複雜化么。

Vector2 3 4,然後把共用的操作做成非成員函數,放到 detail 裡面完事。什麼繼承。。。千萬不要把問題複雜化


https://github.com/FrankPIE/SimpleRayTracing/blob/master/SimpleRayTracing/Vector.hpp

reference x()
{
static_assert(cElemNumber &>= 1, "invaild x reference");
return vec_[0];
}

const_reference x() const
{
static_assert(cElemNumber &>= 1, "invaild x reference");
return vec_[0];
}

reference y()
{
static_assert(cElemNumber &>= 2, "invaild y reference");
return vec_[1];
}

const_reference y() const
{
static_assert(cElemNumber &>= 2, "invaild y reference");
return vec_[1];
}

reference z()
{
static_assert(cElemNumber &>= 3, "invaild z reference");
return vec_[2];
}

const_reference z() const
{
static_assert(cElemNumber &>= 3, "invaild z reference");
return vec_[2];
}

reference w()
{
static_assert(cElemNumber &>= 4, "invaild w reference");
return vec_[3];
}

const_reference w() const
{
static_assert(cElemNumber &>= 4, "invaild w reference");
return vec_[3];
}

給你個參考,你的這些想法,基本在這裡面都實現了,我這個寫法也是參考 @叛逆者KlayGE的數學庫完成的。

這裡的做法很簡單,用static_assert判斷其是否存在x,y,z,w元素,x,y,z,w的獲取是函數而不是成員變數。當你N &< 4的時候,獲取w,編譯就會通不過,就是這樣。


我的建議是設計一個向量基類,用來做通用的運算(常用運算都要求參與運算的向量維度相等吧?)

其中包括xyzw(至少包含四個元素)。

然後繼承出來vec2、vec3等根據要求有選擇性的提供訪問的範圍。

三維向量當作四維來存儲應該沒有問題,內部運算反正都按照下標來。

為了性能,四維以內甚至八維以內的向量還可以通過專門的優化手段去計算。

比如:

temeplete&
class ALIGN16 VecBase
{
protected:
union
{
__m128 dat;
float data[N];
struct
{
float x, y, z, w;
};
};

VecBase() { };
};

然後四維的vec4可以這樣:

class ALIGN16 Vec4 :public VecBase&<4&>
{
public:
using VecBase::x; using VecBase::y; using VecBase::z; using VecBase::w;
Vec4() { }
}

三維vec3可以這樣:

class ALIGN16 Vec3 :public VecBase&<3&>
{
public:
using VecBase::x; using VecBase::y; using VecBase::z;
Vec3() { }
}

拿我之前的設計隨手改了一下,我覺得應該是沒問題的……


僅僅為了名字就特化了那模板的意義在哪裡呢。。。我覺得考慮用繼承配合重載operator[]比較現實


Data/Tuple.hs (逃


首先肯定下,Vector2D、Vector3D、Vector4D的寫法肯定可以的,雖然不知道能不能稱得上Best Practice。(我大UE4的都是FVector和FVector2D……所以你瞎擔心啥?)

然後,

#include &

using namespace std;

template&
struct Vector {
union {
int values[D];
} v;
};

template&<&>
struct Vector&<1&> {
union {
struct {
int x;
} specs;
int values[1];
} v;
};

template&<&>
struct Vector&<2&> {
union {
struct {
int x, y;
} specs;
int values[2];
} v;
};

template&<&>
struct Vector&<3&> {
union {
struct {
int x, y, z;
} specs;
int values[3];
} v;
};

template&<&>
struct Vector&<4&> {
union {
struct {
int x, y, z, w;
} specs;
int values[4];
} v;
};

int main() {
Vector&<4&> v4;
v4.v.values[0] = 0;
v4.v.values[1] = 1;
v4.v.values[2] = 2;
v4.v.values[3] = 3;
cout &<&< v4.v.specs.x &<&< endl; cout &<&< v4.v.specs.y &<&< endl; cout &<&< v4.v.specs.z &<&< endl; cout &<&< v4.v.specs.w &<&< endl; return 0; }

既然你想要,我給你就是。(雖然我這實現肯定不是Best Practice)

另外贊同下裴草莓的回答,static_assert這個我沒想到,C++博大精深啊……


前排搶答

關於 @sovi 寫的union的未定義問題。

template& struct Name_X
{
operator float()
{
return (*(T*)((char*)this - (char*)((((T*)(nullptr))-&>X))))[0];
}
float operator=(float o)
{
operator float() = o;
return operator float();
}
};
template& struct Name_Y
{
operator float()
{
return (*(T*)((char*)this - (char*)((((T*)(nullptr))-&>Y))))[1];
}
float operator=(float o)
{
operator float() = o;
return operator float();
}
};

template& struct Name_Z
{
operator float()
{
return (*(T*)((char*)this - (char*)((((T*)(nullptr))-&>Z))))[2];
}
float operator=(float o)
{
operator float() = o;
return operator float();
}
};

template& struct Name_W
{
operator float()
{
return (*(T*)((char*)this - (char*)((((T*)(nullptr))-&>W))))[3];
}
float operator=(float o)
{
operator float() = o;
return operator float();
}
};

struct Data233
{
float UI[100];
union
{
struct
{
Name_X& X;
Name_Y& Y;
Name_Z& Z;
Name_W& W;
};
float T[4];
};
float operator[](size_t i) { return T[i]; }
};

int main()
{

Data233 a;
a.T[0] = 10086.0f;
a.T[1] = 100.0f;
a.T[2] = 200.0f;
a.T[3] = 300.0f;
cout &<&< static_cast&(a.X) &<&< endl; cout &<&< static_cast&(a.Y) &<&< endl; cout &<&< static_cast&(a.Z) &<&< endl; cout &<&< static_cast&(a.W) &<&< endl; a.X = 2100.0f; a.Y = 2200.0f; a.Z = 2300.0f; a.W = 2400.0f; cout &<&< static_cast&(a.X) &<&< endl; cout &<&< static_cast&(a.Y) &<&< endl; cout &<&< static_cast&(a.Z) &<&< endl; cout &<&< static_cast&(a.W) &<&< endl; }

這種方法貌似行得通?


實現題主的所有要求是可能的。實現思路是通過模板實現 Vector 結構與操作的分離。

首先,定義幾個結構:

// 通用結構
template &
struct StructDefault {
T a[N];
};

// Vector2 結構
template &
struct StructVector2 {
union {
T a[2];
struct { T x, y; };
};
};

// Vector3 結構
template &
struct StructVector3 {
union {
T a[3];
struct { T x, y, z; };
};
};

定義通用的模板類 Vector:

// 通過繼承模板參數 S 實現自定義結構
template &&>
class Vector: public S {
public:
using S::a;
Vector() = default;
Vector(std::initializer_list& il) {
std::copy(il.begin(), il.end(), a);
}

// 定義通用的默認操作函數
T operator [] (int i) { return a[i]; }
const T operator [] (int i) const { return a[i]; }
friend Vector operator + (const Vector a, const Vector b) {
Vector res;
for (int i = 0; i &< N; ++i) res[i] = a[i] + b[i]; return res; } };

定義 Vector2 和 Vector3:

// 不需要定義針對性的操作函數,只需要自定義結構,就直接 using
template &
using Vector2 = Vector&&>;

// 需要定義針對性的操作函數,就繼承後覆蓋之
template &
class Vector3: public Vector&&> {
public:
using S = StructVector3&;
using S::x;
using S::y;
using S::z;
Vector3() = default;
Vector3(T _x, T _y, T _z) {
x = _x; y = _y; z = _z;
}
// 針對 Vector3 定義操作
friend Vector3 operator + (const Vector3 a, const Vector3 b) {
return { a.x + b.x, a.y + b.y, a.z + b.z };
}
};

完整代碼見 https://gist.github.com/zlsun/29f59c3487f0605e8ec948904bea6734


粗暴一點的(如果不是非得要用template的話

#define x a[0]

#define y a[1]

#define z a[2]

#define w a[3]


c++拙劣的繼承讓很多人程序都不會寫了。

vec2,vec3,vec4...vecn 它們的共性就是 float val[D]; 所以你要設計內積,外積,矩陣之類的,直接拿數組指針就行了。搞什麼雞肋啊 。

簡單就是美 , 不得不說這種煩齪的設計是反人類的。 在c裡面根本不用費時間思考的東西,在c++里要想半天,圖個啥?


虛幻4,cocos2dx等許多遊戲引擎直接vector2,3,4,多方便


可以去參考下 tgt 庫


https://github.com/kbdsbx/ZGL/blob/master/ZGL/linear/vector.h

可以結合他的父類矩陣看:

https://github.com/kbdsbx/ZGL/blob/master/ZGL/linear/matrix.h

特化成仿射矩陣可以參考:

https://github.com/kbdsbx/ZGL/blob/master/ZGL/geometry/affine_vector.h

/// The vector or dot that is in affine transfromations
/// 仿射變換中的向量或點
///
/// dim: Dimensions of vector or dot
/// 向量或點維度
/// Titem: Type of item within vector or dot
/// 向量項或點項類型
template &< z_size_t dim, typename Titem &>
class affine_vector
: public vector&< dim + 1, Titem &> {

w是一個特殊的量,所以其實你可以從結構上體現抽象,比如說dim+1

希望可以對你有所幫助。


參考了下 @zlsun 的回答,得到了這段代碼,全部代碼參見https://gist.github.com/peijunz/3d1315251b884a7a8437a5dda46dfd86:

//基類base_vector

template &
struct base_vector{
static_assert(N&>1, "Dimension should be positive!
");
char a[N];
};

template &
struct base_vector&{
union{
struct{T x,y;};
T a[2];
};
};
template &
struct base_vector&{
union{
struct{T x,y,z;};
T a[3];
};
};
template &
struct base_vector&{
union{
struct{T x,y,z,w;};
T a[4];
};
};

可以看到,在基類中已經定義好了相應的x,y,z,w。

//子類Vector
template &
class Vector:public base_vector&{
public:
Vector()=default;
explicit Vector(initializer_list& l){
copy(begin(l), end(l), a);
}
using base_vector&::a;
T norm(){
T _norm=0;
for(int i=0;i&

如果你非要用a.x,a.y,a.z,a.w來表達a[0],a[1],a[2],a[3]的話,

你只能用一個帶x,y,z,w四個成員的結構,然後用[]操作符分別返回裡面的成員,

#include &
#include &

template&
class vec
{
public:
T arr[N];
inline T operator[](int i)
{
return arr[i];
}
};

template&
class vec&
{
public:
T x, y, z, w;
inline T operator[](int i)
{
switch (i)
{
case 0:
return x;
case 1:
return y;
case 2:
return z;
case 3:
return w;
default:
throw std::runtime_error("error");
}
}
};

int main()
{
using namespace std;

cout &<&< "sizeof(vec&) " &<&< sizeof(vec&) &<&< endl; cout &<&< "sizeof(vec&) " &<&< sizeof(vec&) &<&< endl; vec& a = { 0,1,2,3,4 };
cout &<&< "a[3] " &<&< a[3] &<&< endl; //cout &<&< a.x; //dosen"t compile vec& b = { 0,1,2,3 };
cout &<&< "b[0] " &<&< b[0] &<&< endl; cout &<&< "b.x " &<&< b.x &<&< endl; //cout &<&< b[4]; //runtime error cin.get(); }


推薦閱讀:

如何寫個程序找出現有C++工程里的所有函數?
為什麼int型的負數會比字元串函數length()返回的值大?
C++ 用輸入的變數作為數組長度是壞習慣嗎?
在oi/acm中,有什麼冷門但好用的stl函數?
如何向完全不懂編程的小夥伴解釋「程序寫死」?

TAG:編程 | 面向對象編程 | C | 計算機圖形學 |