Что ломает этот шаблон C ++ setter / getter? - PullRequest
5 голосов
/ 26 января 2012

Используя синтаксис GLSL в C ++

Я написал пользовательские векторные классы, такие как vec2, vec3 и т. Д., Которые имитируют типы GLSL и выглядят примерно так:

struct vec3
{
    inline vec3(float x, float y, float z)
      : x(x), y(y), z(z) {}
    union { float x, r, s; };
    union { float y, g, t; };
    union { float z, b, p; };
};

Операции над векторами реализованы следующим образом:

inline vec3 operator +(vec3 a, vec3 b)
{
    return vec3(a.x + b.x, a.y + b.y, a.z + b.z);
}

Это позволяет мне создавать векторы и получать доступ к их компонентам с использованием GLSL-подобного синтаксиса и выполнять операции с ними почти так же, как если бы они были числовыми типами.Союзы позволяют мне относиться к первой координате безразлично как x или как r, как в случае GLSL.Например:

vec3 point = vec3(1.f, 2.f, 3.f);
vec3 other = point + point;
point.x = other.b;

Проблема перепада скорости

Но GLSL также разрешает заклинивать доступ даже с дырами между компонентами.Например, p.yx ведет себя как vec2 с x *1021* и y подкачкой.Когда ни один компонент не повторяется, это также lvalue.Некоторые примеры:

other = point.xyy; /* Note: xyy, not xyz */
other.xz = point.xz;
point.xy = other.xx + vec2(1.0f, 2.0f);

Теперь это можно сделать с помощью стандартных методов получения и установки, таких как vec2 xy() и void xy(vec2 val).Это то, что делает библиотека GLM .

Прозрачный метод получения и установки

Однако я разработал этот шаблон, который позволяет мне делать то же самое в C ++.Поскольку все является POD-структурой, я могу добавить больше объединений:

template<int I, int J> struct MagicVec2
{
    friend struct vec2;
    inline vec2 operator =(vec2 that);

private:
    float ptr[1 + (I > J ? I : J)];
};

template<int I, int J>
inline vec2 MagicVec2<I, J>::operator =(vec2 that)
{
    ptr[I] = that.x; ptr[J] = that.y;
    return *this;
}

и например. класс vec3 становится (я немного упростил вещи, например, ничто не мешает xx от использования здесь как lvalue):

struct vec3
{
    inline vec3(float x, float y, float z)
      : x(x), y(y), z(z) {}

    template<int I, int J, int K>
    inline vec3(MagicVec3<I, J, K> const &v)
      : x(v.ptr[I]), y(v.ptr[J]), z(v.ptr[K]) {}

    union
    {
        struct { float x, y, z; };
        struct { float r, g, b; };
        struct { float s, t, p; };

        MagicVec2<0,0> xx, rr, ss;
        MagicVec2<0,1> xy, rg, st;
        MagicVec2<0,2> xz, rb, sp;
        MagicVec2<1,0> yx, gr, ts;
        MagicVec2<1,1> yy, gg, tt;
        MagicVec2<1,2> yz, gb, tp;
        MagicVec2<2,0> zx, br, ps;
        MagicVec2<2,1> zy, bg, pt;
        MagicVec2<2,2> zz, bb, pp;
        /* Also MagicVec3 and MagicVec4, of course */
    };
};

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

Я очень доволен результатом.Приведенный выше код GLSL работает, и я считаю, что получаю достойную безопасность типов.И я могу #include шейдер GLSL в своем коде C ++.

Ограничения

Конечно, есть ограничения.Мне известны следующие:

  • sizeof(point.xz) будет 3*sizeof(float) вместо ожидаемого 2*sizeof(float).Это сделано специально, и я не знаю, может ли это быть проблематичным.
  • &foo.xz нельзя использовать как vec2*.Это должно быть хорошо, потому что я только когда-либо передаю эти объекты по значению.

Поэтому мой вопрос: Что я мог упустить из виду, что усложнит мою жизнь с этим шаблоном? ТакжеЯ не нашел этот шаблон нигде в другом месте, поэтому, если кто-то знает его имя, я заинтересован.

Примечание: я хочу придерживаться C ++ 98, но я полагаюсь на компилятор, разрешающий наложение типовчерез союзы.Моя причина еще не хотеть C ++ 11 - отсутствие поддержки компилятора на нескольких моих целевых платформах;все компиляторы, которые представляют для меня интерес, поддерживают type punning.

Ответы [ 2 ]

3 голосов
/ 09 февраля 2012

Короче говоря: я думаю, что трудно убедиться, что этот шаблон работает - вот почему вы спрашиваете.Более того, этот шаблон может быть заменен стандартным прокси-шаблоном, правильность которого легче гарантировать.Я должен признать, однако, что издержки хранения решения на основе прокси являются проблемой, когда прокси создаются статически.

Правильность приведенного выше кода

Это код, в котором нет очевидногоошибка;но перефразируя CAR Hoare , это не код, в котором явно нет ошибки.Более того, как трудно убедить себя, что ошибки нет?Я не вижу никакой причины, по которой шаблон не будет работать - но не так легко доказать (даже неофициально), что он будет работать.На самом деле, попытка сделать доказательство может потерпеть неудачу и указать на некоторые проблемы.Чтобы быть в безопасности, я бы отключил все неявно сгенерированные конструкторы / операторы присваивания для классов MagicVecN, просто чтобы не учитывать все связанные с этим сложности (см. Подраздел ниже);однако делать это запрещено, потому что для членов объединения нельзя переопределить неявно определенный оператор присвоения копии, как объяснено в стандартном черновике, который у меня есть, и в сообщении об ошибке GCC:

member ‘MagicVec2<0, 0> vec3::<anonymous union>::xx’ with copy assignment operator not allowed in union

В прилагаемой сущности я вместообеспечить безопасную реализацию вручную.

Обратите внимание, что оператор присваивания MagicVec2 должен принимать свой параметр по ссылке const (см. пример ниже, где это работает);неявные преобразования все еще происходят (ссылка на const будет указывать на созданный временный объект; это не будет работать без квалификатора const).

Почти проблемы, но не совсем

Я думал, что нашел ошибку (что я не сделал), но это все еще несколько интересно рассмотреть - просто посмотреть, сколько случаев должно быть покрыто, чтобы исключить потенциальные ошибки.p.xz = p.zx даст правильные результаты?Я думал, что неявный оператор присваивания MagicVec2 будет вызван, что приведет к неверным результатам;на самом деле, это не так (я считаю), потому что I и J отличаются и являются частью типа.Что когда тип одинаковый?p.xx = q.rr безопасно, но p.xx = p.rr сложно (хотя это может быть глупо, но не должно портить память): неявно генерируемый оператор присваивания memcpy на основе?Кажется, что ответ будет отрицательным, но если да, это будет memcpy между перекрывающимися интервалами памяти, что является неопределенным поведением.

ОБНОВЛЕНИЕ: актуальная проблема

Как заметил ОПоператор по умолчанию для копирования также вызывается для выражения p.xz = q.xz;в этом случае он также скопирует элемент .y.Как упоминалось выше, оператор назначения копирования нельзя отключить или изменить для типов данных, которые являются частью объединения.

Шаблон прокси

Более того, я считаю, что существует гораздо более простое решение, а именношаблон прокси (который вы частично используете).MagicVecX должен содержать указатель на содержащий класс вместо ptr;таким образом, вам не нужно трюк с использованием объединений.

template<int I, int J> struct MagicVec2
{
    friend struct vec2;
    inline MagicVec2(vec2* _this): ptr(_this) {}
    inline vec2 operator=(const vec2& that);
private:
    float *ptr;
};

Я проверил это, скомпилировав (но не связав) этот код, который набрасывает предложенное решение: https://gist.github.com/1775054. Обратите внимание, что код не завершени не проверял - нужно также переопределить конструктор копирования MagicVecX.

3 голосов
/ 28 января 2012

Хорошо, я уже нашел одну проблему, хотя не напрямую с кодом выше. Если vec3 каким-то образом сделан классом шаблона для поддержки , например. int в дополнение к float, и оператор + становится:

template<typename T>
inline vec3<T> operator +(vec3<T> a, vec3<T> b)
{
    return vec3<T>(a.x + b.x, a.y + b.y, a.z + b.z);
}

Тогда этот код не будет работать:

vec3<float> a, b, c;
...
c = a.xyz + b;

Причина в том, что для определения аргументов для + потребуется как вычет аргументов шаблона (T = float) , так и неявное преобразование (из MagicVec3<T,0,1,2> в vec3<T>, что недопустимо.

Однако есть приемлемое для меня решение: напишите все возможные явные операторы.

inline vec3<int> operator +(vec3<int> a, vec3<int> b)
{
    return vec3<int>(a.x + b.x, a.y + b.y, a.z + b.z);
}

inline vec3<float> operator +(vec3<float> a, vec3<float> b)
{
    return vec3<float>(a.x + b.x, a.y + b.y, a.z + b.z);
}

Это также позволит мне определить правила для неявного продвижения, например, я могу решить, что vec3<float> + vec3<int> является законным и вернет vec3<float>.

...