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

How to access members by both name and as an array?

本文关键字:数组 访问 成员 何通过      更新时间:2023-10-16

我有一堆向量类。我有一个二维点vec2_t,一个三维点vec3_t和一个四维点vec4_t(当你做图形时,你经常需要这些;这是图形代码,但问题有一个通用的c++风格)。

现在,我有vec2_t声明两个成员xy;vec3_tvec2_t的子类,并且有第三个成员z;vec4_tvec3_t的子类,并添加w成员。

我有很多近似重复的代码用于运算符重载,比如距离、叉乘、矩阵乘法等等。

我有一些错误,当我错过为子类显式声明操作符时,事情已经被切片,等等。重复的内容让我很烦。

另外,我还想以数组的形式访问这些成员;这对于一些有数组参数的OpenGL函数是有用的。

我想也许有一个vec_t<int dimensions>模板,我可以使我的向量类没有子类化。然而,这引入了两个问题:

  1. 你如何有一个可变数量的成员,也是数组项,并确保他们对齐?我不想失去我的会员;vec.xvec.d[0]要好得多,如果可能的话,我想保留它
  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_tw成员?)

一个可能的解决方案(我认为)。

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;

注:对代码进行了大量更新和改进。

下面的代码使用一个宏来保持代码的整洁,并使用部分专门化来提供成员。它严重依赖于继承,但这使得它很容易扩展到任意维度。它还尽可能地泛型,这就是底层类型是模板参数的原因:

// 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 (/W4适用于MSVC)和-std=c++0x(仅适用于static_assert)上编译干净,没有任何警告。

这是一种方法:

#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的输出,似乎展开了循环,?: s被替换为适当的字段访问。问题是,在一个真实的环境中,比如在一个算法中,这还能被正确处理吗?

一个简单的解决方案可能是最好的:

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;
    }
};

如果您不想自己编写,您可以查看以下建议的一些库:

c++ Vector Math和OpenGL兼容

如果你使用一个特定的编译器,你可以使用非标准的方法,如打包信息或无名结构(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实例化它们。

这是另一种方法。以下工作在gcc c++ 11上:

#include <stdio.h>
struct Vec4
{  
  union
  {
    float raw[4];
    struct {
      float x;
      float y;
      float z;
      float w;
    };
  };
};
int main()
{
  Vec4 v = { 1.f, 2.f, 3.f, 4.f };
  
  printf("%.2f, %.2f, %.2f, %.2fn", v.x, v.y, v.z, v.w);
  printf("%.2f, %.2f, %.2f, %.2fn", v.raw[0], v.raw[1], v.raw[2], v.raw[3]);
  return 0;
}