Sollte Vector3 von Vector2 erben?

18

Ich erstelle ein paar Klassen Vector2(X & Y) und Vector3(X, Y & Z), aber ich weiß nicht , ob machen Vector3von erben Vector2, oder ob neu implementieren die Membervariablen m_xund m_ywieder? Was sind die Vor- und Nachteile jeder Seite (Vererbung vs. Neudefinition)?

Bearbeiten: Ich verwende C ++ (VS2010).

Mark Ingram
quelle
1
Warum schreiben Sie keine allgemeine Vektorklasse für n-dimensionale Vektoren und erben dann (falls erforderlich) eine Klasse von Vektor2 und Vektor3? Sie können auch Vorlagen für die allgemeine Klasse verwenden und Versionen für ganzzahlige Vektoren und Float-Vektoren übernehmen. Bearbeiten: Warum verwenden Sie keine optimierte Mathematikbibliothek?
Danijar
5
"Vector3 ist ein Vector2", beide könnten jedoch von einem übergeordneten VectorN erben
wim 17.10.12
1
Ja, aber dann brauchen Sie wahrscheinlich eine virtuelle Tabelle und das ist einer der Fälle, in denen die Laufzeit- und Speicherkosten eine Rolle spielen können. Idealerweise Vector3sollte a nur 3 sein floats, was den Speicher betrifft. Ich sage nicht, dass das unmöglich ist, nur, dass ich das noch nie in einem Serienmotor gesehen habe.
Laurent Couvidou
2
Ja, ich denke schon. Bis du nichts anderes mehr brauchst floats. Weißt du, YAGNI, KISS, all das Zeug. Vector2, Vector3und Vector4ohne Vererbung und floatsnur ist wirklich der De-facto-Standard in Game-Engines.
Laurent Couvidou
1
Ich hoffe du meintest typedef float real;;).
Mark Ingram

Antworten:

47

Nein, sollte es nicht. Das einzige, was Sie aus der Vererbung verwenden würden, sind die Komponenten xund y. Die in einer Vector2Klasse verwendeten Methoden wären in einer Klasse nicht nützlich Vector3. Sie würden wahrscheinlich unterschiedliche Argumente verwenden und Operationen mit einer unterschiedlichen Anzahl von Mitgliedsvariablen ausführen.

MichaelHouse
quelle
+1, ich sollte mehr auf das Popup achten, damit ich nicht überflüssiges Zeug schreibe.
Matsemann
8
Überbeanspruchung der klassischen Vererbung . Ein Vector3IS-NOT-A Vector2(also sollte es nicht erben), aber ein AppleIS-A Fruit(also kann es erben). Wenn Sie Ihren Verstand genug verdrehen, ist ein Vector3HAS-A Vector2drin, aber der Leistungsverlust und die Schwierigkeitsgradcodierung bedeuten, dass Sie für Vector3und vollständig separate Klassen schreiben Vector2.
Bobobobo
Aber Sie könnten (und sollten meiner Meinung nach) eine n-dimensionale Vektorklasse schreiben, um einen 2D-Vektor und einen 3D-Vektor davon zu erben.
Danijar
8

Mit C ++ können Sie eine kuriose Sache machen (Sie haben keine Sprache angegeben, und diese Antwort ist hauptsächlich deshalb so, weil ich finde, dass es schön ist, Alternativen zu sehen, obwohl ich glaube, dass dies in den meisten Fällen nicht wirklich nützlich ist.)

Mit Vorlagen können Sie Folgendes tun:

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

und das kann so verwendet werden:

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

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

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

Wie gesagt, ich halte dies für nicht nützlich und es wird wahrscheinlich Ihr Leben verkomplizieren, wenn Sie versuchen, Punkte / Normalisierungen / andere Dinge zu implementieren und generisch mit einer beliebigen Anzahl von Vektoren zu arbeiten.

Luke B.
quelle
Ja, all die Generizität scheint nett zu sein, nur die meiste Zeit braucht man nur einen Standard-3-Vektor mit 3 Fließkomma-Komponenten - alle spitzen Klammern werden Vector3f vein bisschen aufblähenVector3<float> v
Bobobobo
@ Bobobobo Ja, ich bin damit einverstanden. Meine Vektorklassen sind in der Regel vec2 und vec3 ohne übergeordnetes Element, aber sie erstellen weiterhin Vorlagen. Wenn es Sie stört, Vector3 <float> zu schreiben, können Sie es immer eingeben
Luke B.
..Und nun das Argument des C-Programmierers .. "aber wie sieht es mit der erhöhten Kompilierzeit für die Verwendung von Vorlagen aus?" Lohnt es sich in diesem Fall wirklich?
Bobobobo
@bobobobo Ich hatte nie ein Problem mit meinen Kompilierzeiten: P, aber ich habe nie an einem Projekt gearbeitet, bei dem die Kompilierzeit ein Problem darstellen würde. Man könnte argumentieren, dass die Kompilierungszeiten es rechtfertigen, keine Floats zu verwenden, wenn Sie Ganzzahlen benötigen.
Luke B.
@bobobobo Mit expliziten Instantiierungen und ohne Ihre Inline-Datei im Header werden die Kompilierungszeiten nicht anders sein. Darüber hinaus ist das Aufblähen der Schablonenwinkel nur einen Schritttypedef entfernt.
Samaursa
7

Unabhängig von der Geschwindigkeit ist die erste Frage, die Sie sich stellen sollten, wenn Sie eine Vererbung durchführen, ob Sie sie polymorph verwenden werden. Genauer gesagt, gibt es eine Situation, in der Sie sich vorstellen können, wie Sie a verwenden, Vector3als wäre es ein Vector2(was Sie explizit sagen, wenn Sie davon erben, dass ein Vektor3 ein Vektor2 ist).

Wenn nicht, sollten Sie keine Vererbung verwenden. Sie sollten keine Vererbung verwenden, um Code freizugeben. Dafür sind Komponenten und externe Funktionen gedacht, nicht dass Sie irgendeinen Code zwischen ihnen teilen würden.

That being said, können Sie einfache Möglichkeiten wollen convert Vector3 s Vector2s, und in diesem Fall können Sie einen Operator Überlastung schreiben, die implizit gestutzt werden , Vector3um einen Vector2. Aber du solltest nicht erben.

Tetrade
quelle
Vielen Dank, ich denke, das hat das Problem hervorgehoben. Ich habe dies aus der Sicht der Codefreigabe betrachtet (dh ich musste die X- und Y-Werte nicht "neu eingeben").
Mark Ingram
+1 gute Antwort, es gibt keine polymorphe Verwendung zwischen Vektoren unterschiedlicher Größe.
Luke B.
Dies ist die größte Sache, die ich meiner eigenen Antwort hinzufügen wollte - mit Sicherheit +1. (Obwohl es seltsame Umstände gibt, unter denen ich mir vorstellen kann , Polymorphismus zu wollen - z. B. 2.5d-Spiele mit Höhenkarten, bei denen Dinge wie Entfernungsprüfungen, Pfade usw. kanonisch in 2d ausgeführt werden sollen, Sie jedoch immer noch 3D-Koordinaten für Objekte angeben müssen)
Steven Stadnicki
@LukeB. Obwohl ich im OP-Fall zustimme, dass es keinen Grund zu erben gibt, Vector2sondern von einer Basis zu erben Vector<N>? Das macht durchaus Sinn. Warum bedeutet Vererbung außerdem automatisch polymorphes Verhalten? Eines der besten Dinge an C ++ ist, dass Sie keine Kosten für die Vererbung haben können. Keine Notwendigkeit, fügen Sie alle virtuellen Methoden (einschließlich virtuelle Destruktoren) in der Vector<N>Basisklasse.
Samaursa
5

Nein, da auch jede Methode überschrieben werden muss, können Sie nicht wirklich davon erben.

Wenn überhaupt, könnten beide eine Vector-Schnittstelle implementieren. Da Sie jedoch wahrscheinlich nicht / sub / dot / dst zwischen einem Vector2 und einem Vector3 hinzufügen möchten, hat dies unerwünschte Nebenwirkungen. Und verschiedene Parameter usw. zu haben, wäre ein Ärger.
Daher kann ich in diesem Fall wirklich keine Vorteile der Vererbung / Schnittstelle erkennen.

Ein Beispiel ist das Libgdx-Framework, bei dem Vector2 und Vector3 nichts anderes miteinander zu tun haben als die gleichen Methoden.

Matsemann
quelle
2

Wenn Sie vorhaben, SIMD- Arrays zu verwenden, sind diese wahrscheinlich die besten. Wenn Sie die Operatorüberladung dennoch verwenden möchten, können Sie erwägen, ein Interface / Mixin zu verwenden, um auf das zugrunde liegende Array zuzugreifen. Hier ist beispielsweise ein Ausgangspunkt, der nur das (nicht getestete) enthält Add.

Beachten Sie, wie ich habe nicht vorgesehen X/ Y/ Zjede VectorXKlasse erben würde direkt von diesem - aus den gleichen Gründen von anderen Menschen angegeben. Trotzdem habe ich Arrays gesehen, die oft in freier Wildbahn als Vektoren verwendet wurden.

#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);
    }
};

Haftungsausschluss: Mein C ++ könnte scheiße sein, es ist schon eine Weile her, seit ich es benutzt habe.

Jonathan Dickinson
quelle
Warten Sie, Mann . Bedeutet Ihre Verwendung, _aligned_mallocdass der Fehler, den ich geöffnet habe, nicht wirklich ein Fehler ist?
Bobobobo
Sie sollten keine Zeiger-Casts verwenden, um Ihre Werte in das __m128Register zu bekommen , _mm_loadu_pssondern stattdessen verwenden. Eine gute Beispielklasse finden Sie hier unter "vectorclass.zip"
bobobobo 17.10.12
@bobobobo Ich werde versuchen, eine Änderung vorzunehmen - beachte dabei den Haftungsausschluss;).
Jonathan Dickinson
@bobobobo _mm_loadu_pssollte für Sie mit dieser Struktur funktionieren (wo _mm_load_psnicht). Ich habe auch Ihren Vorschlag hinzugefügt - zögern Sie nicht, die Frage zu bearbeiten, wenn Sie das Gefühl haben, dass ich den falschen Baum belle (es ist eine Weile her, seit ich C [++] verwendet habe).
Jonathan Dickinson
1

Ein weiterer schwerwiegender Nachteil, wenn Vec3 von Vec2 erbt oder wenn beide von einer einzigen Vector-Klasse erben: Ihr Code wird viel bewirkenvon Operationen an Vektoren, oft in zeitkritischen Situationen, und es liegt sehr in Ihrem Interesse, sicherzustellen, dass all diese Operationen so schnell wie möglich sind - viel mehr als bei vielen anderen Objekten, die dies nicht tun ganz so universell oder niedrig. Während ein guter Compiler sein Bestes tut, um den Vererbungsaufwand zu reduzieren, verlassen Sie sich immer noch mehr auf den Compiler, als Sie möchten. Stattdessen würde ich sie als Strukturen mit so wenig Aufwand wie möglich erstellen und möglicherweise sogar versuchen, die meisten Funktionen, die sie verwenden (mit Ausnahme von Dingen wie operator +, denen nicht wirklich geholfen werden kann), als globale statt als Methoden zu definieren struct. Eine frühzeitige Optimierung wird generell gegen und aus gutem Grund empfohlen,

Steven Stadnicki
quelle
1
-1 weil: Klasse und Struktur nur in C ++ Zugriffsberechtigungen haben und das OP ohnehin keine Sprache angegeben hat, nicht-virtuelle Member-Funktionen die gleichen Auswirkungen auf die Leistung haben wie Nicht-Member-Funktionen und virtuelle Member-Funktionen (die möglicherweise vorhanden sind) Die Probleme, um die Sie sich Sorgen machen, existieren nur, wenn Sie sie lösen, nicht einfach durch Nutzung der Vererbung.
2
@JoshPetrie Gültige Punkte an allen Fronten; Ich neige dazu, C / C ++ als Standardeinstellung zu verwenden, und habe die Frage daher durch dieses Objektiv gesehen. Ich kann glauben , es gibt legitime Leistung (wie auch konzeptionelle) Gründe für die nicht das Erbe Weg zu gehen, wohlgemerkt, aber ich hätte viel besser auf den spezifischen Details gewesen. Ich werde versuchen, dies noch einmal zu überprüfen und zu sehen, ob ich eine bessere Buchhaltung geben kann.
Steven Stadnicki