2011-12-16 29 views
2

我有一堆向量類。我有一個2D點vec2_t,一個3D點vec3_t和一個4D點vec4_t(當你有圖形時,你經常需要這些;這是圖形代碼,但是這個問題有一個通用的C++風格)。如何通過名稱和數組訪問成員?

因爲它是現在,我有vec2_t宣佈兩個成員xy; vec3_t小類vec2_t並且具有第三成員z; vec4_t小類vec3_t並添加了w成員。

我有很多近似重複的代碼,以使運算符重載計算的東西,如距離,交叉乘積,乘以矩陣等等。

我已經有了一些錯誤,當我錯過了爲子類聲明一個操作符等信息時,sliced。重複錯誤我。

此外,我想要訪問這些成員作爲一個數組;這對於一些具有數組參數的OpenGL函數很有用。

我想,也許與vec_t<int dimensions>模板我可以使我的矢量類沒有子類。但是,這會引入兩個問題:

  1. 如何獲得也是數組條目的可變數目的成員,並確保它們對齊?我不想失去我的名字; vec.x遠遠好於vec.d[0]或任何imo,我想盡可能保持它
  2. 當你採用模板化路線時,如何在CPP源文件中使用更多昂貴的方法而不是頭文件?

一種方法是這樣的:

struct vec_t { 
    float data[3]; 
    float& x; 
    float& y; 
    float& z; 
    vec_t(): x(data[0]), y(data[1]), z(data[2]) {} 
}; 

這裏,正確使用別名名稱數組成員,但我已經與(GCC)測試的編譯器似乎沒有工作了他們只是別名,所以班級規模相當大(對於我可能有一組數據,並希望通過例如VBO;因此大小是一個大問題),你將如何模板參數化它,所以只有vec4_t有一個w會員?)

+1

如果你不介意一些編譯器的特殊性,你可以使用[這個問題]中顯示的方法之一(http://stackoverflow.com/questions/1537964/visual-c-equivalent-of-gccs-attribute-packed)來確保在不使用數組的情況下打包數據成員。 – 2011-12-16 13:32:13

回答

0

這將是做到這一點的一種方法:

#include<cstdio> 

class vec2_t{ 
public: 
    float x, y; 
    float& operator[](int idx){ return *(&x + idx); } 
}; 

class vec3_t : public vec2_t{ 
public: 
    float z; 
}; 

編輯:@aix是正確的,說這是不規範的,並可能導致的問題。也許更合適的解決辦法則是:

class vec3_t{ 
public: 
    float x, y, z; 

    float& operator[](int idx){ 

     static vec3_t v; 
     static int offsets[] = { 
      ((char*) &(v.x)) - ((char*)&v), 
      ((char*) &(v.y)) - ((char*)&v), 
      ((char*) &(v.z)) - ((char*)&v)}; 

     return *((float*) ((char*)this+offsets[idx])); 
    } 
}; 

編輯#2:我有一個替代方案,在有可能只寫你的運營商一次,而不是一個更大的類中結束,就像這樣:

#include <cstdio> 
#include <cmath> 

template<int k> 
struct vec{ 

}; 

template<int k> 
float abs(vec<k> const&v){ 
    float a = 0; 
    for (int i=0;i<k;i++) 
     a += v[i]*v[i]; 
    return sqrt(a); 
} 

template<int u> 
vec<u> operator+(vec<u> const&a, vec<u> const&b){ 
    vec<u> result = a; 
    result += b; 
    return result; 
} 

template<int u> 
vec<u>& operator+=(vec<u> &a, vec<u> const&b){ 
    for (int i=0;i<u;i++) 
     a[i] = a[i] + b[i]; 
    return a; 
} 

template<int u> 
vec<u> operator-(vec<u> const&a, vec<u> const&b){ 
    vec<u> result; 
    for (int i=0;i<u;i++) 
     result[i] = a[i] - b[i]; 
    return result; 
} 

template<> 
struct vec<2>{ 
    float x; 
    float y; 
    vec(float x=0, float y=0):x(x), y(y){} 
    float& operator[](int idx){ 
     return idx?y:x; 
    } 
    float operator[](int idx) const{ 
     return idx?y:x; 
    } 
}; 

template<> 
struct vec<3>{ 
    float x; 
    float y; 
    float z; 

    vec(float x=0, float y=0,float z=0):x(x), y(y),z(z){} 
    float& operator[](int idx){ 
     return (idx==2)?z:(idx==1)?y:x; 
    } 
    float operator[](int idx) const{ 
     return (idx==2)?z:(idx==1)?y:x; 
    } 
}; 

有一些問題,但:

1)我不知道你怎麼去界定左右的成員函數,而無需編寫它們(或至少某種存根)不止一次。

2)它依賴於編譯器優化。我查看了g++ -O3 -S的輸出,看起來循環得到展開,並且?:被替換爲正確的字段訪問。問題是,在一個算法中,這是否仍然可以在真實環境下正確處理?

+2

聰明,因爲這是肯定它依賴於未定義的行爲?可以在`x`和`y`之間填充,更不用說在內存中圍繞`z`的位置的假設。 – NPE 2011-12-16 11:58:06

+0

它基本上是subclasaing方法 - 如何去除運算符並避免切片? – Will 2011-12-16 12:14:15

+0

我更喜歡你的第二次編輯;通過用operator []模擬一個數組,你如何安全地將它作爲一個數組傳遞給一個想要數組的函數? – Will 2011-12-16 14:26:11

2

一個可能的解決方案(我認爲)。

main.cpp中:

#include <iostream> 
#include "extern.h" 

template <int S> 
struct vec_t_impl 
{ 
    int values[S]; 
    bool operator>(const vec_t_impl<S>& a_v) const 
    { 
     return array_greater_than(values, a_v.values, S); 
    } 
    void print() { print_array(values, S); } 

    virtual ~vec_t_impl() {} 
}; 

struct vec_t2 : vec_t_impl<2> 
{ 
    vec_t2() : x(values[0]), y(values[1]) {} 
    int& x; 
    int& y; 
}; 

struct vec_t3 : vec_t_impl<3> 
{ 
    vec_t3() : x(values[0]), y(values[1]), z(values[2]) {} 
    int& x; 
    int& y; 
    int& z; 
}; 

int main(int a_argc, char** a_argv) 
{ 
    vec_t3 a; 
    a.x = 5; 
    a.y = 7; 
    a.z = 20; 

    vec_t3 b; 
    b.x = 5; 
    b.y = 7; 
    b.z = 15; 

    a.print(); 
    b.print(); 

    cout << (a > b) << "\n"; 

    return 0; 
} 

extern.h:

extern bool array_greater_than(const int* a1, const int* a2, const size_t size); 
extern void print_array(const int* a1, const size_t size); 

extern.cpp:

#include <iostream> 

bool array_greater_than(const int* a1, const int* a2, const size_t size) 
{ 
    for (size_t i = 0; i < size; i++) 
    { 
     if (*(a1 + i) > *(a2 + i)) 
     { 
      return true; 
     } 
    } 
    return false; 
} 

void print_array(const int* a1, const size_t size) 
{ 
    for (size_t i = 0; i < size; i++) 
    { 
     if (i > 0) cout << ", "; 
     std::cout << *(a1 + i); 
    } 
    std::cout << '\n'; 
} 

編輯:

在試圖解決的大小問題你可以改變會員參考nce變量到返回引用的成員函數。

struct vec_t2 : vec_t_impl<2> 
{ 
    int& x() { return values[0]; } 
    int& y() { return values[1]; } 
}; 

缺點,這是有點奇怪代碼:

vec_t2 a; 
a.x() = 5; 
a.y() = 7; 
0

一個簡單的解決方案可能是最好的位置:

struct Type 
{ 
    enum { x, y }; 
    int values[2]; 
}; 

Type t; 
if (t.values[0] == t.values[Type::x]) 
    cout << "Good"; 

你也可以做這樣的事情:

struct Type 
{ 
    int values[2]; 

    int x() const { 
     return values[0]; 
    } 

    void x(int i) { 
     values[0] = i; 
    } 
}; 
1

注:更新和改進了很多代碼。

以下代碼使用宏來保持代碼清潔和部分專業化以提供成員。它很大程度上依賴於繼承性,但這使得將其擴展到任意維度非常容易。它也旨在儘可能通用的,這就是爲什麼基礎類型是一個模板參數:

// forward declaration, needed for the partial specializations 
template<unsigned, class> class vec; 

namespace vec_detail{ 
// actual implementation of the member functions and by_name type 
// partial specializations do all the dirty work 
template<class Underlying, unsigned Dim, unsigned ActualDim = Dim> 
struct by_name_impl; 

// ultimate base for convenience 
// this allows the macro to work generically 
template<class Underlying, unsigned Dim> 
struct by_name_impl<Underlying, 0, Dim> 
{ struct by_name_type{}; }; 

// clean code after the macro 
// only need to change this if the implementation changes 
#define GENERATE_BY_NAME(MEMBER, CUR_DIM) \ 
    template<class Underlying, unsigned Dim> \ 
    struct by_name_impl<Underlying, CUR_DIM, Dim> \ 
     : public by_name_impl<Underlying, CUR_DIM - 1, Dim> \ 
    { \ 
    private: \ 
     typedef vec<Dim, Underlying> vec_type; \ 
     typedef vec_type& vec_ref; \ 
     typedef vec_type const& vec_cref; \ 
     typedef by_name_impl<Underlying, CUR_DIM - 1, Dim> base; \ 
    protected: \ 
     struct by_name_type : base::by_name_type { Underlying MEMBER; }; \ 
     \ 
    public: \ 
     Underlying& MEMBER(){ \ 
      return static_cast<vec_ref>(*this).member.by_name.MEMBER; \ 
     } \ 
     Underlying const& MEMBER() const{ \ 
      return static_cast<vec_cref>(*this).member.by_name.MEMBER; \ 
     } \ 
    } 

GENERATE_BY_NAME(x, 1); 
GENERATE_BY_NAME(y, 2); 
GENERATE_BY_NAME(z, 3); 
GENERATE_BY_NAME(w, 4); 

// we don't want no pollution 
#undef GENERATE_BY_NAME 
} // vec_detail:: 

template<unsigned Dim, class Underlying = int> 
class vec 
    : public vec_detail::by_name_impl<Underlying, Dim> 
{ 
public: 
    typedef Underlying underlying_type; 

    underlying_type& operator[](int idx){ 
     return member.as_array[idx]; 
    } 

    underlying_type const& operator[](int idx) const{ 
     return member.as_array[idx]; 
    } 

private: 
    typedef vec_detail::by_name_impl<Underlying, Dim> base; 
    friend struct vec_detail::by_name_impl<Underlying, Dim>; 
    typedef typename base::by_name_type by_name_type; 

    union{ 
     by_name_type by_name; 
     underlying_type as_array[Dim]; 
    } member; 
}; 

用法:

#include <iostream> 

int main(){ 
    typedef vec<4, int> vec4i; 
    // If this assert triggers, switch to a better compiler 
    static_assert(sizeof(vec4i) == sizeof(int) * 4, "Crappy compiler!"); 
    vec4i f; 
    f.w() = 5; 
    std::cout << f[3] << '\n'; 
} 

當然你也可以讓工會公衆,如果你想,但我認爲通過該功能訪問成員更好。

注:上面的代碼完全編譯而不對MSVC10,GCC 4.4.5和3.1鏘任何警告與-Wall -Wextra/W4爲MSVC)和-std=c++0x(僅用於static_assert)。

0

如果你不想自己動手寫,你可以檢查一些庫的建議上:

C++ Vector Math and OpenGL compatable

如果你使用一個特定的編譯器,你可以使用非標準方法,如包裝信息或無名結構(Visual Studio中):

union Vec3 
{ 
    struct {double x, y, z;}; 
    double v[3]; 
}; 

在另一方面,鑄造幾個成員變量的陣列似乎危險的,因爲編譯器可能改變類佈局。

所以邏輯解決方案似乎有一個數組,並使用方法來訪問該數組。例如:

template<size_t D> 
class Vec 
{ 
private: 
    float data[D]; 

public: // Constants 
    static const size_t num_coords = D; 

public: // Coordinate Accessors 
    float& x()    { return data[0]; } 
    const float& x() const { return data[0]; } 
    float& y()    { static_assert(D>1, "Invalid y()"); return data[1]; } 
    const float& y() const { static_assert(D>1, "Invalid y()"); return data[1]; } 
    float& z()    { static_assert(D>2, "Invalid z()"); return data[2]; } 
    const float& z() const { static_assert(D>2, "Invalid z()"); return data[2]; } 

public: // Vector accessors 
    float& operator[](size_t index) {return data[index];} 
    const float& operator[](size_t index) const {return data[index];} 

public: // Constructor 
    Vec() { 
    memset(data, 0, sizeof(data)); 
    } 

public: // Explicit conversion 
    template<size_t D2> 
    explicit Vec(const Vec<D2> &other) { 
    memset(data, 0, sizeof(data)); 
    memcpy(data, other.data, std::min(D, D2)); 
    } 
}; 

使用上述類,可以使用[]操作者訪問構件陣列,座標使用存取方法X()中,y(),Z()。使用顯式的轉換構造函數可以防止切片。它使用static_assert禁用較低維度的訪問器。如果您不使用C++ 11,則可以使用Boost.StaticAssert

您也可以使用模板化方法。您可以使用作爲以便將它們擴展到N維或使用遞歸調用。例如,爲了計算平方和:

template<size_t D> 
struct Detail 
{ 
    template<size_t C> 
    static float sqr_sum(const Vec<D> &v) { 
    return v[C]*v[C] + sqr_sum<C-1>(v); 
    } 

    template<> 
    static float sqr_sum<0>(const Vec<D> &v) { 
    return v[0]*v[0]; 
    } 
}; 

template<size_t D> 
float sqr_sum(const Vec<D> &v) { 
    return Detail<D>::sqr_sum<D-1>(v); 
} 

上面的代碼可用於:

int main() 
{ 
    Vec<3> a; 
    a.x() = 2; 
    a.y() = 3; 
    std::cout << a[0] << " " << a[1] << std::endl; 
    std::cout << sqr_sum(a) << std::endl;; 

    return 0; 
} 

爲了防止模板膨脹,可能會在一段cpp的代碼你的模板方法和實例他們爲D = 1,2,3,4 4.