Как получить доступ к членам как по имени, так и в виде массива? - PullRequest
2 голосов
/ 16 декабря 2011

У меня есть куча векторных классов.У меня есть 2D-точка vec2_t, 3D-точка vec3_t и 4D-точка vec4_t (они часто нужны, когда у вас есть графика; это графический код, но вопрос имеет общий характер C ++).

Как и сейчас, я vec2_t объявляю двух членов x и y;vec3_t подклассы vec2_t и имеет третьего члена z;vec4_t подклассы vec3_t и добавляет w член.

У меня много почти дублированного кода для перегрузки операторов, вычисляющих такие вещи, как расстояния, перекрестные произведения, умножение на матрицу и т. Д.

У меня было несколько ошибок, когда вещи были нарезаны , когда я пропустил явное объявление оператора для подкласса и так далее.И дублирование вызывает у меня ошибку.

Кроме того, я хочу получить доступ к этим элементам как к массиву;это было бы полезно для некоторых функций OpenGL, которые имеют параметры массива.

Я предполагаю, что, возможно, с помощью шаблона vec_t<int dimensions> я могу создавать свои векторные классы без подклассов.Однако это создает две проблемы:

  1. Каким образом у вас есть переменное число элементов, которые также являются элементами массива, и убедитесь, что они совпадают?Я не хочу терять своих названных членов;vec.x намного лучше, чем vec.d[0] или любой другой, и я хотел бы сохранить его, если это возможно
  2. Как у вас есть много более дорогих методов в исходном файле 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 член?)

Ответы [ 5 ]

2 голосов
/ 16 декабря 2011

Возможное решение (я думаю).

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

EDIT:

Пытаясь решить проблему с размером, вы можете изменить ссылочные переменные-члены на функции-члены, возвращающие ссылку.

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 голос
/ 16 декабря 2011

Примечание: Обновлен и значительно улучшен код.

В следующем коде макрос используется для поддержания чистоты кода и частичной специализации для предоставления членов. Он сильно зависит от наследования, но это позволяет очень легко распространить его на произвольные измерения. Он также должен быть как можно более универсальным, поэтому базовый тип является параметром шаблона:

// 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 ).

0 голосов
/ 17 декабря 2011

Если вы не хотите писать это самостоятельно, вы можете проверить некоторые из библиотек, предлагаемых для:

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.

0 голосов
/ 16 декабря 2011

Простое решение может быть лучшим здесь:

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;
    }
};
0 голосов
/ 16 декабря 2011

Это может быть один из способов сделать это:

#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 заменены на правильные обращения к полям.Вопрос в том, будет ли это по-прежнему правильно обрабатываться в реальном контексте, скажем, в алгоритме?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...