如何通过名称和数组访问成员?
我有一堆向量类。我有一个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
会员?)
这将是做到这一点的一种方法:
#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
的输出,看起来循环得到展开,并且?:
被替换为正确的字段访问。问题是,在一个算法中,这是否仍然可以在真实环境下正确处理?
一个可能的解决方案(我认为)。
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';
}
编辑:
在试图解决的大小问题你可以改变会员参考nce变量到返回引用的成员函数。
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;
一个简单的解决方案可能是最好的位置:
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;
}
};
注:更新和改进了很多代码。
以下代码使用宏来保持代码清洁和部分专业化以提供成员。它很大程度上依赖于继承性,但这使得将其扩展到任意维度非常容易。它也旨在尽可能通用的,这就是为什么基础类型是一个模板参数:
// 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和3.1锵任何警告与-Wall -Wextra
(/W4
为MSVC)和-std=c++0x
(仅用于static_assert
)。
如果你不想自己动手写,你可以检查一些库的建议上:
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
您也可以使用模板化方法。您可以使用作为以便将它们扩展到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 4.
如果你不介意一些编译器的特殊性,你可以使用[这个问题]中显示的方法之一(http://*.com/questions/1537964/visual-c-equivalent-of-gccs-attribute-packed)来确保在不使用数组的情况下打包数据成员。 – 2011-12-16 13:32:13