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

Posted

技术标签:

【中文标题】如何通过名称和数组访问成员?【英文标题】:How to access members by both name and as an array? 【发布时间】:2011-12-16 11:44:13 【问题描述】:

我有一堆矢量类。我有一个 2D 点 vec2_t、一个 3D 点 vec3_t 和一个 4D 点 vec4_t(你在做图形时经常需要这些;这是图形代码,但这个问题具有通用的 C++ 风格)。

就像现在一样,我有vec2_t 声明了两个成员xyvec3_t 子类vec2_t 并有第三个成员zvec4_t 子类vec3_t 并添加一个w 成员。

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

当我错过为子类显式声明运算符时,我遇到了一些错误,事情一直是sliced 等等。重复让我很烦。

另外,我也想以数组的形式访问这些成员;这对于某些具有数组参数的 OpenGL 函数很有用。

我想也许使用vec_t<int dimensions> 模板我可以在没有子类化的情况下创建我的向量类。但是,这会带来两个问题:

    如何拥有可变数量的成员同时也是数组条目,并确保它们对齐?我不想失去我的指定成员; vec.xvec.d[0] 或任何 imo 好得多,如果可能的话,我想保留它 当您采用模板路线时,如何在 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 成员?)

【问题讨论】:

如果您不介意一些编译器的特殊性,您可以使用this question 中显示的方法之一来确保在不使用数组的情况下打包数据成员。 【参考方案1】:

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

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;

【讨论】:

这样还是会有引用成员导致class-size臃肿的问题。【参考方案2】:

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

以下代码使用宏来保持代码干净,并使用部分特化来提供成员。它在很大程度上依赖于继承,但这使得它很容易扩展到任意维度。它还旨在尽可能通用,这就是底层类型是模板参数的原因:

// 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)和-std=c++0x(仅@987654326)编译干净,没有任何警告@)。

【讨论】:

如何模板参数化它? @Will:再次清理并更新了帖子。泛型ftw。 :) 虽然目前无法将高维 vec 转换为低维。明天某个时候我也会处理这个问题。【参考方案3】:

这是一种方法:

#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 被正确的字段访问替换。问题是,在真实的环境中,比如在算法中,这仍然可以正确处理吗?

【讨论】:

这么聪明,它肯定依赖于未定义的行为吗? xy 之间可能存在填充,更不用说围绕内存中 z 位置的假设了。 它基本上是子类化方法 - 如何对运算符进行重复数据删除并避免切片? 我更喜欢你的第二次编辑;通过使用 operator[] 模拟数组,如何安全地将其用作数组以传递给需要数组的函数? 不幸的是,我不认为你可以,如果它想要float*。但是,它可以与只要求其参数可索引的模板函数一起使用。【参考方案4】:

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

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

【讨论】:

【参考方案5】:

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

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。

【讨论】:

【参考方案6】:

这是另一种方法。以下适用于 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, %.2f\n", v.x, v.y, v.z, v.w);
  printf("%.2f, %.2f, %.2f, %.2f\n", v.raw[0], v.raw[1], v.raw[2], v.raw[3]);

  return 0;

【讨论】:

以上是关于如何通过名称和数组访问成员?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不知道名称的情况下访问结构成员?

如何从类变量数组中访问类成员?

如何使用 JavaScript spread... 语法更改对象的一个​​字段,该字段属于数组并通过名称-值对访问?

无论如何使用包含数组名称的字符串来访问/修改数组?

C ++继承类具有同名成员

如何从 SKNode.children 数组访问 SKNode 子类方法?