如何通过名称和数组访问成员?

时间:2011-12-16 11:44:13

标签: c++ templates

我有一堆矢量类。我有一个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.xvec.d[0]或者其他任何东西都要好得多,如果可能的话,我想保留它
  2. 当您采用模板化路线时,如何在CPP源文件中使用许多更昂贵的方法而不是头文件?

  3. 一种方法是:

    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成员?)

5 个答案:

答案 0 :(得分: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';
}

编辑:

为了解决大小问题,您可以将成员引用变量更改为返回引用的成员函数。

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;

答案 1 :(得分: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和Clang 3.1上使用-Wall -Wextra(MSVC为/W4)和{{1}进行干净编译,没有任何警告}(仅适用于-std=c++0x)。

答案 2 :(得分: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的输出,似乎循环被展开,?:被替换为正确的字段访问。问题是,这仍然可以在真实环境中正确处理,例如在算法中吗?

答案 3 :(得分: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;
    }
};

答案 4 :(得分: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

你也可以模板化你的方法。您可以使用 for 将它们扩展为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。