我有一堆矢量类。我有一个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]
或者其他任何东西都要好得多,如果可能的话,我想保留它一种方法是:
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
成员?)
答案 0 :(得分:2)
一种可能的解决方案(我认为)。
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;
答案 1 :(得分:1)
注意:更新并改进了很多代码。
以下代码使用宏来保持代码清洁和部分特化以提供成员。它在很大程度上依赖于继承,但这使得将其扩展到任意维度变得非常容易。它也旨在尽可能通用,这就是基础类型是模板参数的原因:
// 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
)和{{1}进行干净编译,没有任何警告}(仅适用于-std=c++0x
)。
答案 2 :(得分:0)
这是一种方法:
#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
的输出,似乎循环被展开,?:
被替换为正确的字段访问。问题是,这仍然可以在真实环境中正确处理,例如在算法中吗?
答案 3 :(得分:0)
一个简单的解决方案可能是最好的解决方案:
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;
}
};
答案 4 :(得分:0)
如果您不想自己编写,可以查看一些建议的库:
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。