Должен ли Vector3 наследоваться от Vector2?


18

Я создаю пару классов Vector2(X & Y) и Vector3(X, Y & Z), но я не знаю, делать ли Vector3наследование Vector2, или заново реализовать переменные-члены m_xи m_yснова? Каковы плюсы и минусы каждой стороны (наследование против переопределения).

Изменить: я использую C ++ (VS2010).


1
Почему бы не написать общий векторный класс для n размерных векторов, а затем (при необходимости) наследовать классы vector2 и vector3. Вы также можете использовать шаблоны для общего класса и наследовать версии для целочисленных векторов и векторов с плавающей точкой. Изменить: Почему вы не используете оптимизированную математическую библиотеку?
Данияр

5
Не смотря на то, что «Vector3 - это Vector2», они оба могут наследовать от родительского VectorN
wim

1
Да, но тогда вам, вероятно, понадобится виртуальная таблица, и это один из случаев, когда затраты времени и памяти могут иметь значение. В идеале, a Vector3должно быть всего 3, floatsесли речь идет о памяти. Не сказать, что это невозможно, просто я никогда не видел этого в серийном движке.
Лоран Кувиду

2
Да, я так думаю. До тех пор, пока вам больше ничего не нужно floats. Вы знаете, ЯГНИ, ПОЦЕЛУЙ, все эти вещи. Vector2, Vector3И Vector4без каких - либо наследования и floatsтолько на самом деле де - факто стандартом в игровых системах.
Лоран Кувиду

1
Я надеюсь, что вы имели в виду typedef float real;;).
Марк Ингрэм

Ответы:


47

Нет, не должно. Единственное , что вы хотите использовать от наследования является xи yкомпонентов. Методы, используемые в Vector2классе, не будут полезны в Vector3классе, они, вероятно, будут принимать разные аргументы и выполнять операции с другим числом переменных-членов.


+1, я должен уделять больше внимания всплывающему окну, чтобы я не писал лишние вещи.
Мацеманн

8
Классическое наследование . Vector3IS-НЕ-А Vector2(так он не должен наследовать), но AppleIS-A Fruit(так что он может наследовать). Если вы достаточно Vector3напряглись, Vector2в нем есть HAS-A , но потеря производительности и сложность кодирования означают, что вы будете писать совершенно отдельные классы для Vector3и Vector2.
Бобобобо

Но вы могли бы (на мой взгляд, следует) написать класс n-мерного вектора, чтобы унаследовать от него 2-мерный вектор и 3-й вектор.
Данияр

8

Есть любопытная вещь, которую вы можете сделать с C ++ (вы не указали язык, и этот ответ в основном потому, что я думаю, что приятно видеть альтернативы, хотя я не очень верю, что это полезно в большинстве случаев.)

Используя шаблоны, вы можете сделать что-то вроде этого:

template <class T, class S, int U>
class VectorN
{
    protected:
        int _vec[U];
    public:
        S& operator+=(const S c)
        {
            for(int i = 0; i < U; i++)
            {
                _vec[i] += c.at(i);
            }
            return (S&)*this;
        }
        int at(int n) const
        {
            return _vec[n];
        }
};

template <class T>
class Vec2 : public VectorN<T,Vec2<T>,2>
{
    public:
        T& x;
        T& y;
        Vec2(T a, T b) : x(this->_vec[0]), y(this->_vec[1])
        {
            this->_vec[0] = a;
            this->_vec[1] = b;
        }
};

template <class T>
class Vec3 : public VectorN<T,Vec3<T>,3>
{
    public:
        T& x;
        T& y;
        T& z;
        Vec3(T a, T b, T c) : x(this->_vec[0]), y(this->_vec[1]), z(this->_vec[2])
        {
            this->_vec[0] = a;
            this->_vec[1] = b;
            this->_vec[2] = c;
        }
};

и это можно использовать так:

int main(int argc, char* argv[])
{

    Vec2<int> v1(5,0);
    Vec2<int> v2(10,1);

    std::cout<<((v1+=v2)+=v2).x;
    return 0;
}

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


Да, все общие черты кажутся хорошими, только в большинстве случаев вам нужен только стандартный 3-вектор с 3 компонентами с плавающей запятой - все угловые скобки сделают Vector3f vнемного более распухшимиVector3<float> v
bobobobo

@bobobobo Да, я согласен. Мои векторные классы обычно имеют vec2 и vec3 без родителя, но все же делают их шаблонами. Если вас беспокоит написание Vector3 <float>, вы всегда можете его определить
Люк Б.

..А теперь аргумент программиста на Си .. "а как насчет увеличения времени компиляции для использования шаблонов ??" Это действительно того стоит в этом случае?
Бобобобо

@bobobobo У меня никогда не было проблем со временем компиляции: P, но я никогда не работал над проектом, в котором время компиляции было бы проблемой. Можно утверждать, что время компиляции оправдывает не использование float, когда вам нужны целые числа.
Люк Б.

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

7

Независимо от скорости, первый вопрос, который вы должны задать себе при выполнении любого наследования, это то, собираетесь ли вы использовать их полиморфно. Точнее говоря, есть ли ситуация, когда вы можете видеть себя, использующего, Vector3как если бы это было Vector2(что, наследуя от него, вы явно говорите, что Vector3 "is-a" Vector2).

Если нет, то вы не должны использовать наследование. Вы не должны использовать наследование для совместного использования кода. Для этого и нужны компоненты и внешние функции, а не то, что вы все равно будете делиться между ними кодом.

При этом вам могут потребоваться простые способы преобразования Vector3 s в Vector2s, и в этом случае вы можете написать перегрузку оператора, которая будет неявно обрезать Vector3a Vector2. Но вы не должны наследовать.


Спасибо, я думаю, что это выдвинуло на первый план проблему, я смотрел на это с точки зрения «совместного использования кода» (то есть не необходимости «перепечатывать» значения X & Y).
Марк Ингрэм

+1 отличный ответ, нет полиморфного использования между векторами разных размеров.
Люк Б.

Это самая большая вещь, которую я собирался добавить к своему собственному ответу - +1 точно. (Хотя есть странные обстоятельства, при которых я могу представить, что хочу полиморфизма - например, 2.5d «карты высот», где такие вещи, как проверка расстояния, траектория и т. Д. Канонически захотят сделать в 2d, но вам все равно нужно предоставить 3D-координаты для объектов)
Стивен Стадницки

@LukeB. Хотя в случае с ОП я согласен с тем, что, похоже, нет причин наследовать, Vector2а наследовать от базы Vector<N>? Это имеет смысл. Кроме того, почему наследование автоматически означает полиморфное поведение? Одна из лучших вещей в C ++ - то, что вы можете иметь нулевое наследование затрат. Нет необходимости добавлять какие-либо виртуальные методы (включая виртуальные деструкторы) в базовый Vector<N>класс.
Самаурса

5

Нет, так как каждый метод также должен быть переопределен, вы не будете использовать его наследование.

Если что-то, они оба могут реализовать интерфейс Vector. Однако, поскольку вы, вероятно, не хотите добавлять / sub / dot / dst между Vector2 и Vector3, это будет иметь нежелательные побочные эффекты. И иметь разные параметры и т. Д. Было бы хлопот.
Так что я действительно не вижу никаких плюсов наследования / интерфейса в этом случае.

Примером является среда Libgdx, где Vector2 и Vector3 не имеют ничего общего друг с другом, кроме как с использованием методов одного типа.


2

Если вы планируете использовать SIMD- массивы , вероятно, лучше всего. Если вы все еще хотите использовать перегрузку операторов, вы можете рассмотреть возможность использования интерфейса / миксина для доступа к базовому массиву - например, вот отправная точка, которая имеет только (непроверенную) Add.

Обратите внимание, что я не предоставил X/ Y/ Z, каждый VectorXкласс наследовал бы напрямую от этого - по тем же причинам, которые были указаны другими людьми. Тем не менее, я видел множество массивов, используемых как векторы много раз в дикой природе.

#include <xmmintrin.h>

class Vector
{
public:
    Vector(void)
    {
        Values = AllocArray();
    }

    virtual ~Vector(void) 
    { 
        _aligned_free(Values);
    }

    // Gets a pointer to the array that contains the vector.
    float* GetVector()
    {
        return Values;
    }

    // Gets the number of dimensions contained by the vector.
    virtual char GetDimensions() = 0;

    // An example of how the Vector2 Add would look.
    Vector2 operator+ (const Vector2& other)
    {
        return Vector2(Add(other.Values));
    }

protected:
    Vector(float* values)
    {
        // Assume it was created correctly.
        Values = values;
    }

    // The array of values in the vector.
    float* Values;

    // Adds another vector to this one (this + other)
    float* Add(float* other)
    {
        float* r = AllocArray();

#if SSE
        __m128 pv1 = _mm_load_ps(Values);
        __m128 pv2 = _mm_load_ps(other);
        __m128 pvr = _mm_load_ps(r);

        pvr = _mm_add_ps(pv1, pv2);
        _mm_store_ps(r, pvr);

#else
        char dims = GetDimensions();
        for(char i = 0; i < dims; i++)
            r[i] = Values[i] + other[i];
#endif

        return r;
    }

private:

    float* AllocArray()
    {
        // SSE float arrays need to be 16-byte aligned.
        return (float*) _aligned_malloc(GetDimensions() * sizeof(float), 16);
    }
};

Отказ от ответственности: мой C ++ может быть отстой, прошло много времени с тех пор, как я его использовал.


Подожди-ка , твое использование _aligned_mallocозначает, что ошибка, которую я открыл, на самом деле не ошибка?
Бобобобо

Вы не должны использовать приведение указателей, чтобы получить ваши значения в __m128регистр, вы должны использовать _mm_loadu_psвместо этого. Хороший пример класса находится здесь под "vectorclass.zip"
bobobobo

@bobobobo Я сделаю лучшую попытку редактирования - обратите особое внимание на заявление об отказе от ответственности;).
Джонатан Дикинсон

@bobobobo _mm_loadu_psдолжен работать для вас с этой структурой (где _mm_load_psне будет). Я также добавил ваше предложение - не стесняйтесь редактировать вопрос, если вы чувствуете, что я лаю не на том дереве (давно я использовал C [++]).
Джонатан Дикинсон

1

Еще один серьезный довод в пользу того, чтобы Vec3 наследовал от Vec2 или, возможно, оба они наследовали от одного класса Vector: ваш код будет делать многоопераций над векторами, часто в критических по времени ситуациях, и в ваших интересах быть уверенными, что все эти операции выполняются настолько быстро, насколько это возможно - намного больше, чем для многих других объектов, которые не довольно универсальный или низкоуровневый. Хотя хороший компилятор сделает все возможное, чтобы сгладить любые издержки наследования, вы все равно больше полагаетесь на компилятор, чем хотели бы; вместо этого я бы построил их как структуры с минимальными накладными расходами и, возможно, даже попытался бы сделать большинство функций, которые их используют (за исключением таких вещей, как operator +, с которыми ничего не поделаешь), глобальными, а не методами STRUCT. Ранняя оптимизация, как правило, рекомендуется против


1
-1 потому что: класс и структура имеют только последствия предоставления доступа в C ++, а OP не указал язык в любом случае, не виртуальные функции-члены имеют те же последствия для производительности, что и функции, не являющиеся членами, и виртуальные функции-члены (которые потенциально могут демонстрировать проблемы, которые вас беспокоят) существуют, только если вы их решаете, а не просто с помощью наследования.

2
@JoshPetrie Действительные точки на всех фронтах; Я склонен «по умолчанию» к C / C ++ и поэтому видел вопрос через этот объектив. Я действительно считаю , что есть законное исполнение (а также концептуальные) причины не идти по пути наследования, заметьте, но я мог бы быть гораздо лучше на конкретных деталях. Я попытаюсь вернуться к этому и посмотреть, смогу ли я дать лучший учет.
Стивен Стадницки,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.