2011-12-16 33 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.