如何通过名称和数组访问成员
How to access members by both name and as an array?
我有一堆向量类。我有一个二维点vec2_t
,一个三维点vec3_t
和一个四维点vec4_t
(当你做图形时,你经常需要这些;这是图形代码,但问题有一个通用的c++风格)。
现在,我有vec2_t
声明两个成员x
和y
;vec3_t
是vec2_t
的子类,并且有第三个成员z
;vec4_t
是vec3_t
的子类,并添加w
成员。
我有很多近似重复的代码用于运算符重载,比如距离、叉乘、矩阵乘法等等。
我有一些错误,当我错过为子类显式声明操作符时,事情已经被切片,等等。重复的内容让我很烦。
另外,我还想以数组的形式访问这些成员;这对于一些有数组参数的OpenGL函数是有用的。
我想也许有一个vec_t<int dimensions>
模板,我可以使我的向量类没有子类化。然而,这引入了两个问题:
- 你如何有一个可变数量的成员,也是数组项,并确保他们对齐?我不想失去我的会员;
vec.x
比vec.d[0]
要好得多,如果可能的话,我想保留它 - 当您采用模板路由时,您如何在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
成员?)
一个可能的解决方案(我认为)。
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;
}
- 将静态字符数组中的字符分配给动态分配的字符数组 - 访问冲突
- 从数组访问SDL_Rect成员时垃圾值?
- 从 C++ 中的 int ** 数组访问元素
- 使用动态数组访问冲突写入位置
- 如何使用对象数组访问类中的变量?
- 从链表数组访问节点数据
- 返回类型的数组访问
- 在 C++ 中使用指针的数组:访问返回的数组时出现分段错误
- 为什么2D数组访问要比1D阵列访问更快
- 指向数组访问的指针上的隔离错误
- C 通过对象的指针数组访问对象方法
- C 通过数组访问多个成员
- C++可变参数模板数组/访问元素
- 数组访问导致空指针取消引用
- 无法从类数组访问数组
- 为什么编译器允许越界数组访问,即使使用 constexpr 索引也是如此
- C++ 从数组访问重载构造函数
- 为什么为了简单的数组访问,我必须在g++中打开优化
- 如何在重载数组访问运算符时访问SIMD矢量元素
- C++大型2D数组访问冲突