利用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個元素叫做w3. 希望實現這個效果只是為了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&
{
operator float()
{
return (*(T*)((char*)this - (char*)((((T*)(nullptr))-&>X))))[0];
}
float operator=(float o)
{
operator float() = o;
return operator float();
}
};
template&
{
operator float()
{
return (*(T*)((char*)this - (char*)((((T*)(nullptr))-&>Y))))[1];
}
float operator=(float o)
{
operator float() = o;
return operator float();
}
};
template&
{
operator float()
{
return (*(T*)((char*)this - (char*)((((T*)(nullptr))-&>Z))))[2];
}
float operator=(float o)
{
operator float() = o;
return operator float();
}
};
template&
{
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&
Name_Y&
Name_Z&
Name_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&
實現題主的所有要求是可能的。實現思路是通過模板實現 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&
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 & 可以看到,在基類中已經定義好了相應的x,y,z,w。 不過作為多參數模板,成員函數沒法對部分參數進行專門化(這是個坑)。只能通過其他方法,對N為某個值時進行特殊實現。不過考慮到N是模板參數,函數內部寫if(N=4)之類的應該能被優化掉。
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];
};
};
//子類Vector
template &
class Vector:public base_vector&
public:
Vector()=default;
explicit Vector(initializer_list&
copy(begin(l), end(l), a);
}
using base_vector&
T norm(){
T _norm=0;
for(int i=0;i&
opengl第八版的項目文件里有個vmath,好像實現的不錯
union不是你們這樣用的,標準的未定義行為,Union declaration
it"s undefined behavior to read from the member of the union that wasn"t most recently written
如果你非要用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&
cout &<&< "a[3] " &<&< a[3] &<&< endl;
//cout &<&< a.x; //dosen"t compile
vec&
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函數?
※如何向完全不懂編程的小夥伴解釋「程序寫死」?