如何通过名称和数组访问成员?
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
声明了两个成员x
和y
; vec3_t
子类vec2_t
并有第三个成员z
; vec4_t
子类vec3_t
并添加一个w
成员。
我有很多近乎重复的代码,用于运算符重载计算诸如距离、叉积、矩阵乘法等。
当我错过为子类显式声明运算符时,我遇到了一些错误,事情一直是sliced 等等。重复让我很烦。
另外,我也想以数组的形式访问这些成员;这对于某些具有数组参数的 OpenGL 函数很有用。
我想也许使用vec_t<int dimensions>
模板我可以在没有子类化的情况下创建我的向量类。但是,这会带来两个问题:
-
如何拥有可变数量的成员同时也是数组条目,并确保它们对齐?我不想失去我的指定成员;
vec.x
比 vec.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 被正确的字段访问替换。问题是,在真实的环境中,比如在算法中,这仍然可以正确处理吗?
【讨论】:
这么聪明,它肯定依赖于未定义的行为吗?x
和 y
之间可能存在填充,更不用说围绕内存中 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;
【讨论】:
以上是关于如何通过名称和数组访问成员?的主要内容,如果未能解决你的问题,请参考以下文章