Чи повинен наслідувати Vector3 від Vector2?


18

Я створюю пару класів Vector2(X & Y) і Vector3(X, Y & Z), але я не знаю, чи слід Vector3успадковувати Vector2, чи потрібно повторно реалізувати змінні члена m_xі m_yзнову? Які плюси і мінуси кожної сторони (успадкування проти перегляду).

Редагувати: я використовую C ++ (VS2010).


1
Чому б не написати загальний векторний клас для n розмірних векторів, а потім (у разі потреби) успадкувати вектор2 та клас вектор3. Ви також можете використовувати шаблони для загального класу та успадковувати версії для цілих векторів і векторів з поплавком. Редагувати: Чому ви не використовуєте оптимізовану математичну бібліотеку?
danijar

5
Ні по натяжкою «Vector3 є Vector2», вони обидва могли успадковувати від батьківського VectorN хоча
Вім

1
Так, але тоді, ймовірно, вам знадобиться віртуальна таблиця, і це один із випадків, коли витрати на виконання та пам'ять можуть мати значення. В ідеалі, об'єм пам'яті Vector3повинен бути лише 3 floats. Не кажучи, що це неможливо, тільки що я ніколи цього не бачив у виробничому двигуні.
Лоран Кувіду

2
Так, я так думаю. До тих пір, поки нічого іншого вам не знадобиться floats. Знаєте, ЯГНІ, KISS, все це. Vector2, Vector3і Vector4без спадщини, і floatsлише це справді є фактичним стандартом у ігрових двигунах.
Лоран Кувіду

1
Сподіваюся, ви мали на увазі typedef float real;;).
Марк Інграм

Відповіді:


47

Ні, це не повинно. Єдине, що ви використовували б у спадщині - це компоненти xта yкомпоненти. Методи, які використовуються в Vector2класі, не будуть корисними для Vector3класу, вони, ймовірно, прийматимуть різні аргументи та виконуватимуть операції над різною кількістю змінних членів.


+1, я мушу більше уваги звертати на спливаюче вікно, щоб я не писав зайвих матеріалів.
Мацеман

8
Класичне спадкове використання . Vector3IS-НЕ-А Vector2(так він не повинен наслідувати), але AppleIS-A Fruit(так що він може успадковувати). Якщо ви достатньо закрутили свій розум, Vector3HAS-A Vector2в ньому, але втрата продуктивності та кодування труднощів означають, що ви будете писати абсолютно окремі класи для Vector3та Vector2.
бобобобо

Але ви можете (на мою думку, слід) написати n-мірний векторний клас, щоб успадкувати 2d-вектор і 3d-вектор від цього.
danijar

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 <flolo> вас турбує, ви завжди можете набрати це
Luke B.

..А тепер аргумент програміста C .. "а як щодо збільшення часу компіляції для використання шаблонів ??" Чи справді це варто в цьому випадку?
бобобобо

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

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

7

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

Якщо ні, то не слід використовувати спадщину. Ви не повинні використовувати спадщину для спільного використання коду. Саме для цього потрібні компоненти та зовнішні функції, а не те, що ви б ділилися між собою будь-яким кодом.

Зважаючи на це, ви можете захотіти прості способи перетворення Vector3 s в Vector2s, і в цьому випадку ви можете написати перевантаження оператора, яка буде неявно усічена в Vector3a Vector2. Але не слід успадковувати.


Дякую, я думаю, що це висвітлило проблему, я дивився на це з точки зору "спільного використання коду" (тобто не потрібно "повторно вводити" значення X & Y).
Марк Інграм

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

Це найбільше, що я збирався додати до власної відповіді - +1 точно. (Хоча існують дивні обставини, за яких я можу уявити, що хочеться поліморфізму - наприклад, 2.5d ігри з висотою, де такі речі, як перевірка відстані, проходження маршрутів тощо), канонічно хочуть зробити в 2d, але вам все одно потрібно надати 3d координати для об'єктів)
Стівен Стадницький

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

5

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

Якщо щось, вони могли б реалізувати векторний інтерфейс. Однак, оскільки ви, мабуть, не хочете додавати / sub / dot / dst між Vector2 та Vector3, це матиме небажані побічні ефекти. І мати різні параметри тощо було б клопоту.
Тому я справді не бачу жодних плюсів успадкування / інтерфейсу в цьому випадку.

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


2

Якщо ви плануєте використовувати SIMD- масиви , ймовірно, найкращі. Якщо ви все ще хочете використовувати перевантаження оператора, ви можете скористатися інтерфейсом / mixin для доступу до базового масиву - наприклад, ось початкова точка, яка має лише (неперевірений) 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означає ти використання помилки, яку я відкрив , насправді не помилка?
bobobobo

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

@bobobobo Я зроблю найкращу спробу редагування - зверніть особливу увагу на відмову від відповідальності;).
Джонатан Дікінсон

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

1

Ще одна серйозна проблема, коли Vec3 успадковує від Vec2 або, можливо, спадковує з одного класу Vector: ваш код буде робити багатооперацій над векторами, часто у критичних для часу ситуаціях, і вам дуже важливо переконатися, що всі ці операції проходять настільки швидко, наскільки це можливо - набагато більше, ніж це стосується багатьох інших об'єктів, які не є цілком універсальний або низькорівневий. Хоча хороший компілятор зробить усе можливе, щоб вирівняти будь-які надбавки за спадщиною, ви все ще більше покладаєтесь на компілятор, ніж хочете; натомість я б створив їх як структури з якомога меншими накладними витратами і, можливо, навіть спробував би зробити більшість функцій, які ними користуються (за винятком таких речей, як оператор +, яким реально не допомогти), - глобальні, а не методи на структура. Рання оптимізація, як правило, рекомендується проти, і з відмінних причин,


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

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