2008年7月22日星期二

共享算術運算子

每當實作一些有關數學的類別 (如 Vector, Matrix, Point, Size 等...) 時,加減乘除等運算子都會時常出現。讓我們試試把共同的地方提煉成基類別:

template<int N>
class Tuple {
public:
Tuple operator+(const Tuple& rhs) const
{
Tuple result;
for(int i=0; i<N; ++i)
result.data[i] = data[i] + rhs.data[i];
return result;
}

float data[N];
};

class Vec3 : public Tuple<3> {
public:
Vec3(float x, float y, float z) {
data[0] = x; data[1] = y; data[2] = z;
}
};

int main() {
Vec3 v1(1, 2, 3);
Vec3 v2(4, 5, 6);
// Compilation error: v1 + v2 is returning Tuple but not Vec3
Vec3 v3 = v1 + v2;
return 0;
}

大家不用擔心那個回圈會為性能帶來負面影響,編譯器懂得把它優化 (我已在VC2008上證實了這一點)。
但由於運算子的返回型態出了問題,我們作出以下嘗試:

template<int N, class R>
class Tuple {
public:
R operator+(const Tuple& rhs) const
{
R result;
for(int i=0; i<N; ++i)
result.data[i] = data[i] + rhs.data[i];
return result;
}

float data[N];
};

class Vec3 : public Tuple<3, Vec3> {
public:
Vec3(float x, float y, float z) {
data[0] = x; data[1] = y; data[2] = z;
}
};

int main() {
Vec3 v1(1, 2, 3);
Vec3 v2(4, 5, 6);
Vec3 v3 = v1 + v2;
return 0;
}

非常好。不過還可以更好哩:

template<int N, class R, class U>
class Tuple : public U {
public:
R operator+(const Tuple& rhs) const
{
R result;
for(int i=0; i<N; ++i)
result.data[i] = data[i] + rhs.data[i];
return result;
}
};

struct _Vec3Union {
union {
struct { float x, y, z; };
float data[3];
};
};

class Vec3 : public Tuple<3, Vec3, _Vec3Union> {
public:
Vec3() {}
Vec3(float x_, float y_, float z_) {
x = x_; y = y_; z = z_;
}
};

struct _SizeUnion {
union {
struct { float width, height; };
float data[2];
};
};

class Size : public Tuple<2, Size, _SizeUnion> {
public:
Size() {}
Size(float w, float h) {
width = w; height = h;
}
};

int main() {
Vec3 v1(1, 2, 3);
Vec3 v2(4, 5, 6);
Vec3 v3 = v1 + v2;

Size s1(1, 2);
Size s2(2, 3);
Size s3 = s1 + s2;

return 0;
}

這可讓 Vec3 組成的 x, y 和 z 用方便的形式去存取。

儘管以上的提示未必有多大用途,還望它能加強大家對 C++ 的了解。

4 則留言:

  1. 1. 會有padding 的問題嗎??

    2. 可以應用於 SIMD 嗎 ??

    回覆刪除
  2. 1. 用家需要於 _XXXUnion 裡自行解決Padding 的問題.

    2. 不太可行. 你可以把 __m128 混入 _XXXUnion 裡, 但基類別 Tuple 須要處理不同長度的結構. 你可以在 Vec4 裡加入特化來達到效果, 但這就不是我想強調的共享罷了.

    回覆刪除
  3. Vector, Point 和其他型別所需要的 operators 都不一樣. 例如 Vector 有 Add, Subtract, Dot. Point 應該只有 Subtract, 而 Subtract 的 return type 是 Vector.

    要做到這樣是不是可以...

    template<int N, T> class Vector<N, T> :
    public Add<N, Vector<N, T> >,
    public Subtract<N, Vector<N, T> >,
    public Dot<N, T>
    {
    }

    但這樣好像不能實現, 因為 data 只應該有一份.

    Template Meta Programming... 還是簡單些好?

    回覆刪除
  4. 唔...有道理. 也許只能用於 Vector 和 Matrix, 非常有限.
    所以最後我說了一句: "儘管以上的提示未必有多大用途,還望它能加強大家對 C++ 的了解." :)

    回覆刪除