Vector3 dovrebbe ereditare da Vector2?


18

Sto creando un paio di classi Vector2(X e Y) e Vector3(X, Y e Z), ma non so se fare l' Vector3ereditarietà Vector2o se implementare nuovamente le variabili membro m_xe di m_ynuovo? Quali sono i pro e i contro di ciascuna parte (eredità vs ridefinizione).

Modifica: sto usando C ++ (VS2010).


1
Perché non scrivere una classe vettoriale generale per i vettori n dimensionali e quindi (se necessario) ereditare una classe vector2 e vector3. Puoi anche usare modelli per la classe generale ed ereditare versioni anche per vettori interi e vettori float. Modifica: perché non usi una libreria matematica ottimizzata?
danijar,

5
In nessun modo dell'immaginazione "Vector3 è un Vector2", entrambi potrebbero ereditare da un genitore VectorN però
wim

1
Sì, ma probabilmente avrai bisogno di una tabella virtuale e questo è uno dei casi in cui i costi di runtime e memoria possono avere importanza. Idealmente, a Vector3dovrebbe essere solo 3 per floatsquanto riguarda la memoria. Non dire che è impossibile, solo che non l'ho mai visto in un motore di produzione.
Laurent Couvidou,

2
Sì, penso di si. Fino a quando non avrai bisogno di nient'altro floats. Sai, YAGNI, KISS, tutta quella roba. Vector2, Vector3e Vector4senza eredità e floatssolo in realtà è lo standard di fatto nei motori di gioco.
Laurent Couvidou,

1
Spero che volessi dire typedef float real;;).
Mark Ingram,

Risposte:


47

No non dovrebbe. L'unica cosa che useresti dall'eredità sono i componenti xe y. I metodi utilizzati in una Vector2classe non sarebbero utili in una Vector3classe, probabilmente prenderebbero argomenti diversi ed eseguiranno operazioni su un diverso numero di variabili membro.


+1, dovrei prestare maggiore attenzione al popup in modo da non scrivere cose ridondanti.
Matsemann,

8
Abuso di eredità classica . Un Vector3IS-NOT-A Vector2(quindi non deve ereditare), ma un AppleIS-A Fruit(quindi può ereditare). Se distorci abbastanza la tua mente, un Vector3HAS-A Vector2in esso, ma la perdita di prestazioni e la codifica di difficoltà significano che scriverai classi completamente separate per Vector3e Vector2.
bobobobo,

Ma potresti (e secondo me dovrebbe) scrivere una classe di vettore n-dimensionale per ereditare un vettore 2d e un vettore 3d da quello.
danijar,

8

C'è una cosa curiosa che puoi fare con C ++ (non hai specificato una lingua, e questa risposta è principalmente perché penso che sia bello vedere delle alternative, anche se non credo davvero che sia utile nella maggior parte dei casi.)

Utilizzando i modelli puoi fare qualcosa del genere:

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

e questo può essere usato in questo modo:

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

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

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

Come ho detto, non penso che questo sia utile e probabilmente complicherà la tua vita quando provi a implementare dot / normalize / altre cose e cerchi di essere generico con un numero qualsiasi di vettori.


Sì, tutta la generalità sembra buona, solo la maggior parte delle volte hai solo bisogno di un 3-vettore standard con 3 componenti in virgola mobile - tutte le parentesi angolari renderanno Vector3f vun po 'più Vector3<float> v
gonfio

@bobobobo Sì, sono d'accordo. Le mie classi vettoriali sono di solito vec2 e vec3 senza parent, ma le rendono comunque dei template. Se scrivere Vector3 <float> ti dà fastidio, puoi sempre scriverlo
Luca B.

..E ora l'argomento del programmatore C. .. "ma per quanto riguarda il tempo di compilazione aumentato per l'utilizzo dei template ??" Ne vale davvero la pena in questo caso?
bobobobo,

@bobobobo Non ho mai avuto problemi con i miei tempi di compilazione: P, ma non ho mai lavorato su un progetto che il tempo di compilazione sarebbe stato un problema. Si potrebbe sostenere che i tempi di compilazione giustificano il non utilizzo di float quando sono necessari numeri interi.
Luca B.

@bobobobo Con istanze esplicite e senza includere il file inline nell'intestazione, i tempi di compilazione non saranno diversi. Inoltre, la staffa angolare del modello si gonfia solo di uno typedef.
Samaursa,

7

Indipendentemente dalla velocità, la prima domanda che dovrebbe porsi quando facendo qualsiasi eredità è se si sta andando ad essere li utilizzano polimorfico. Più specificamente, c'è qualche situazione in cui puoi vedere te stesso usando a Vector3come se fosse un Vector2(che, ereditando da esso, stai dicendo esplicitamente che un Vector3 "è-un" Vector2).

Altrimenti, non dovresti usare l'ereditarietà. Non dovresti usare l'ereditarietà per condividere il codice. Ecco a cosa servono i componenti e le funzioni esterne, non che condivideresti comunque un codice tra loro.

Detto questo, potresti voler modi semplici per convertire Vector3 s in Vector2s, e in tal caso puoi scrivere un sovraccarico per l'operatore che troncerà implicitamente il carattere Vector3a a Vector2. Ma non dovresti ereditare.


Grazie, penso che sia stato messo in evidenza il problema, lo stavo osservando dal punto di vista della "condivisione del codice" (ovvero non dover "riscrivere" i valori X e Y).
Mark Ingram,

+1 ottima risposta, non c'è uso polimorfico tra vettori di dimensioni diverse.
Luca B.

Questa è la cosa più grande che avrei aggiunto alla mia risposta, sicuramente +1. (Anche se ci sono strane circostanze in cui posso immaginare di volere un polimorfismo, ad esempio giochi 2.5d 'heightmap' in cui cose come controlli di distanza, percorsi ecc. Vorranno canonicamente essere fatti in 2d ma è comunque necessario fornire coordinate 3d per gli oggetti)
Steven Stadnicki,

@LukeB. Anche se nel caso dei PO concordo sul fatto che sembra non esserci alcun motivo per ereditare Vector2ma ereditare da una base Vector<N>? Questo ha perfettamente senso. Inoltre, perché l'eredità significa automaticamente comportamento polimorfico? Una delle cose migliori di C ++ è che puoi avere un'eredità a costo zero. Non è necessario aggiungere alcun metodo virtuale (inclusi i distruttori virtuali) nella Vector<N>classe base .
Samaursa,

5

No, poiché anche ogni metodo dovrà essere sovrascritto, non avrai alcun modo di ereditare da esso.

Se qualcosa, entrambi potrebbero implementare un'interfaccia Vector. Tuttavia, poiché probabilmente non si desidera aggiungere / sub / punto / dst tra un Vector2 e un Vector3, ciò avrà effetti collaterali indesiderati. E avere parametri diversi ecc. Sarebbe una seccatura.
Quindi in questo caso non riesco davvero a vedere alcun pro di eredità / interfaccia.

Un esempio è il framework Libgdx, in cui Vector2 e Vector3 non hanno nulla a che fare l'uno con l'altro, tranne che avere lo stesso tipo di metodi.


2

Se si prevede di utilizzare gli array SIMD sono probabilmente i migliori. Se desideri ancora utilizzare il sovraccarico dell'operatore, puoi prendere in considerazione l'utilizzo di un'interfaccia / mixin per accedere all'array sottostante - ad esempio, ecco un punto di partenza che ha solo (non testato) Add.

Nota come non ho fornito X/ Y/ Z, ogni VectorXclasse erediterà direttamente da questa - per gli stessi motivi specificati da altre persone. Tuttavia, ho visto matrici usate come vettori molte volte in natura.

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

Disclaimer: il mio C ++ potrebbe fare schifo, è da un po 'che non lo uso.


Aspetta amico , il tuo uso _aligned_mallocsignifica che il bug che ho aperto non è davvero un bug?
bobobobo,

Non dovresti usare i lanciatori di puntatori per inserire i tuoi valori nel __m128registro, dovresti _mm_loadu_psinvece usare . Una buona lezione di esempio è qui sotto "vectorclass.zip"
bobobobo,

@bobobobo Farò del mio meglio per una modifica - presta particolare attenzione al disclaimer;).
Jonathan Dickinson,

@bobobobo _mm_loadu_psdovrebbe funzionare per te con quella struttura (dove _mm_load_psnon lo farà). Ho anche aggiunto il tuo suggerimento: sentiti libero di modificare la domanda se ritieni che stia abbaiando l'albero sbagliato (è da un po 'che non uso C [++]).
Jonathan Dickinson,

1

Un altro serio inconveniente di avere Vec3 ereditare da Vec2 o, probabilmente, di avere entrambi ereditato da una singola classe Vector: il tuo codice farà moltodi operazioni su vettori, spesso in situazioni critiche in termini di tempo, ed è molto nel tuo interesse assicurarti che tutte quelle operazioni siano più veloci che possono essere - molto più di quanto lo sia per molti altri oggetti che non lo sono abbastanza universale o di basso livello. Mentre un buon compilatore farà del suo meglio per appiattire qualsiasi eredità ambientale, stai ancora facendo affidamento sul compilatore più di quanto ti piacerebbe; invece, li costruirei come strutture con il minor sovraccarico possibile e forse proverei persino a fare in modo che la maggior parte delle funzioni che li usano (ad eccezione di cose come l'operatore + che non può davvero essere aiutato) siano globali anziché metodi struct. L'ottimizzazione precoce è generalmente sconsigliata e, con ottimi motivi,


1
-1 perché: class e struct hanno solo implicazioni sulla concessione dell'accesso in C ++ e l'OP non ha specificato comunque un linguaggio, le funzioni membro non virtuali hanno le stesse implicazioni sulle prestazioni delle funzioni non membri e le funzioni membro virtuali (che potenzialmente presentano i problemi di cui ti preoccupi) esistono solo se li risolvi, non semplicemente utilizzando l'eredità.

2
@JoshPetrie Punti validi su tutti i fronti; Tendo a "default" in C / C ++ e quindi ho visto la domanda attraverso quell'obiettivo. Io non credo che ci sono legittimo (oltre che concettuali) ragioni per non andare via eredità, sia chiaro, ma avrei potuto essere molto meglio sui dettagli specifici. Proverò a rivederlo e vedrò se posso dare una migliore contabilità.
Steven Stadnicki,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.