Vector3 doit-il hériter de Vector2?

18

Je crée quelques classes Vector2(X & Y) et Vector3(X, Y & Z), mais je ne sais pas si je dois Vector3hériter de Vector2, ou si je dois réimplémenter les variables membres m_xet m_yencore? Quels sont les avantages et les inconvénients de chaque côté (héritage vs redéfinition).

Edit: j'utilise C ++ (VS2010).

Mark Ingram
la source
1
Pourquoi ne pas écrire une classe vectorielle générale pour n vecteurs dimensionnels et ensuite (si nécessaire) hériter d'une classe vector2 et vector3. Vous pouvez également utiliser des modèles pour la classe générale et hériter des versions pour les vecteurs entiers et les vecteurs flottants. Edit: Pourquoi n'utilisez-vous pas une bibliothèque mathématique optimisée?
danijar
5
Par aucun effort d'imagination "Vector3 est un Vector2", ils pourraient tous les deux hériter d'un parent VectorN
wim
1
Oui, mais vous aurez probablement besoin d'une table virtuelle et c'est l'un des cas où les coûts d'exécution et de mémoire peuvent être importants. Idéalement, un Vector3devrait être seulement 3 floatsen ce qui concerne la mémoire. Je ne dis pas que c'est impossible, juste que je n'ai jamais vu ça dans un moteur de production.
Laurent Couvidou
2
Oui, je le pense. Tant que vous n'avez besoin de rien d'autre floats. Vous savez, YAGNI, KISS, tout ça. Vector2, Vector3et Vector4sans héritage et floatsseulement est vraiment la norme de facto dans les moteurs de jeu.
Laurent Couvidou
1
J'espère que tu voulais dire typedef float real;;).
Mark Ingram

Réponses:

47

Non, ça ne devrait pas. La seule chose que vous utiliseriez de l'héritage est les composants xet y. Les méthodes utilisées dans une Vector2classe ne seraient pas utiles dans une Vector3classe, elles prendraient probablement différents arguments et effectueraient des opérations sur un nombre différent de variables membres.

MichaelHouse
la source
+1, je devrais prêter plus d'attention au popup pour ne pas écrire de trucs redondants.
Matsemann
8
Surutilisation de l'héritage classique . Un Vector3IS-NOT-A Vector2(donc il ne doit pas hériter), mais un AppleIS-A Fruit(donc il peut hériter). Si vous tordez assez votre esprit, un Vector3HAS-A Vector2dedans, mais la perte de performance et le codage difficile signifient que vous écrirez des classes complètement séparées pour Vector3et Vector2.
bobobobo
Mais vous pourriez (et à mon avis, devrait) écrire une classe vectorielle à n dimensions pour hériter d'un vecteur 2D et d'un vecteur 3D de cela.
danijar
8

Il y a une chose curieuse que vous pouvez faire avec C ++ (Vous n'avez pas spécifié de langage, et cette réponse est principalement parce que je pense que c'est agréable de voir des alternatives, bien que je ne pense pas vraiment que cela soit utile dans la plupart des cas.)

En utilisant des modèles, vous pouvez faire quelque chose comme ceci:

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

et cela peut être utilisé comme ceci:

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

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

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

Comme je l'ai dit, je ne pense pas que ce soit utile, et cela compliquera probablement votre vie lorsque vous essayez d'implémenter dot / normaliser / autre et d'essayer d'être générique avec un certain nombre de vecteurs.

Luke B.
la source
Oui, tout le caractère générique semble bien, seulement la plupart du temps, vous n'avez besoin que d'un vecteur standard à 3 composants à virgule flottante - toutes les parenthèses angulaires rendront Vector3f vun peu plus gonfléVector3<float> v
bobobobo
@bobobobo Oui, je suis d'accord. Mes classes vectorielles sont généralement vec2 et vec3 sans parent, mais en font toujours des modèles. Si l'écriture de Vector3 <float> vous dérange, vous pouvez toujours le taper
Luke B.
..Et maintenant l'argument du programmeur C .. "mais qu'en est-il du temps de compilation accru pour l'utilisation des modèles ??" Est-ce que cela en vaut vraiment la peine dans ce cas?
bobobobo
@bobobobo Je n'ai jamais eu de problème avec mes temps de compilation: P, mais je n'ai jamais travaillé sur un projet dont la compilation serait un problème. On pourrait soutenir que les temps de compilation justifient de ne pas utiliser de flottants lorsque vous avez besoin d'entiers.
Luke B.
@bobobobo Avec des instanciations explicites et sans inclure votre fichier en ligne dans l'en-tête, les temps de compilation ne seront pas différents. De plus, le ballonnement du support d'angle du gabarit n'est qu'à un seul typedef.
Samaursa
7

Quelle que soit la vitesse, la première question que vous devez vous poser lorsque vous effectuez un héritage est de savoir si vous allez les utiliser de manière polymorphe. Plus précisément, existe-t-il une situation où vous pouvez vous voir utiliser un Vector3comme si c'était un Vector2(qui, en héritant de celui-ci, vous dites explicitement qu'un Vector3 "est-un" Vector2).

Sinon, vous ne devez pas utiliser l'héritage. Vous ne devez pas utiliser l'héritage pour partager du code. C'est à cela que servent les composants et les fonctions externes, pas que vous partagiez de tout code entre eux de toute façon.

Cela étant dit, vous voudrez peut-être des moyens simples de convertir Vector3 s en Vector2s, et dans ce cas, vous pouvez écrire une surcharge d'opérateur qui tronquera implicitement le Vector3en a Vector2. Mais vous ne devriez pas hériter.

Tetrad
la source
Merci, je pense que cela a mis en évidence le problème, je regardais cela du point de vue du "partage de code" (c'est-à-dire ne pas avoir à "retaper" les valeurs X & Y).
Mark Ingram
+1 bonne réponse, il n'y a pas d'utilisation polymorphe entre des vecteurs de tailles différentes.
Luke B.
C'est la plus grande chose que j'allais ajouter à ma propre réponse - +1 à coup sûr. (Bien qu'il y ait des circonstances étranges dans lesquelles je peux imaginer vouloir un polymorphisme - par exemple, les jeux 2.5d 'heightmap' où des choses comme les vérifications de distance, le cheminement etc. voudront canoniquement être faites en 2d mais vous devez toujours fournir des coordonnées 3D pour les objets)
Steven Stadnicki
@LukeB. Bien que dans le cas des PO, je convienne qu'il ne semble pas y avoir de raison d'hériter Vector2mais d'hériter d'une base Vector<N>? Cela est parfaitement logique. De plus, pourquoi l'héritage signifie-t-il automatiquement un comportement polymorphe? L'une des meilleures choses à propos de C ++ est que vous pouvez avoir un héritage à coût nul. Pas besoin d'ajouter de méthodes virtuelles (y compris des destructeurs virtuels) dans la Vector<N>classe de base .
Samaursa
5

Non, car chaque méthode devra également être remplacée, vous n'aurez donc pas besoin d'en hériter.

Si quelque chose, ils pourraient tous deux implémenter une interface vectorielle. Cependant, comme vous ne voulez probablement pas ajouter / sub / dot / dst entre un Vector2 et un Vector3, cela aura des effets secondaires indésirables. Et avoir différents paramètres, etc. serait un problème.
Donc, je ne vois vraiment aucun avantage de l'héritage / interface dans ce cas.

Un exemple est le framework Libgdx, où Vector2 et Vector3 n'ont rien à voir l'un avec l'autre, à part avoir le même type de méthodes.

Matsemann
la source
2

Si vous prévoyez d'utiliser des baies SIMD sont probablement les meilleures. Si vous souhaitez toujours utiliser la surcharge d'opérateur, vous pouvez envisager d'utiliser une interface / mixin pour accéder au tableau sous-jacent - par exemple, voici un point de départ qui n'a que le (non testé) Add.

Remarquez comment je n'ai pas fourni X/ Y/ Z, chaque VectorXclasse hériterait directement de celle-ci - pour les mêmes raisons spécifiées par d'autres personnes. Pourtant, j'ai vu des tableaux utilisés comme vecteurs à plusieurs reprises dans la nature.

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

Avertissement: Mon C ++ pourrait être nul, cela fait un moment que je ne l'ai pas utilisé.

Jonathan Dickinson
la source
Attendez , est-ce que votre utilisation _aligned_mallocsignifie que le bogue que j'ai ouvert n'est pas vraiment un bogue?
bobobobo
Vous ne devez pas utiliser de transtypage de pointeur pour obtenir vos valeurs dans le __m128registre, vous devez _mm_loadu_psplutôt utiliser Un bon exemple de classe est ici sous "vectorclass.zip"
bobobobo
@bobobobo Je ferai de mon mieux une tentative de modification - faites particulièrement attention à la clause de non-responsabilité;).
Jonathan Dickinson
@bobobobo _mm_loadu_psdevrait fonctionner pour vous avec cette structure (où _mm_load_pspas). J'ai également ajouté votre suggestion - n'hésitez pas à modifier la question si vous pensez que j'aboie le mauvais arbre (cela fait un moment que je n'ai pas utilisé C [++]).
Jonathan Dickinson
1

Un autre inconvénient sérieux pour que Vec3 hérite de Vec2 ou, sans doute, pour que les deux héritent d'une seule classe Vector: votre code fera beaucoupd'opérations sur des vecteurs, souvent dans des situations critiques, et il est dans votre intérêt de vous assurer que toutes ces opérations sont aussi rapides qu'elles le peuvent - beaucoup plus que pour de nombreux autres objets qui ne le sont pas tout à fait si universel ou bas niveau. Alors qu'un bon compilateur fera de son mieux pour aplanir les frais généraux d'héritage, vous comptez toujours plus sur le compilateur que vous ne le souhaiteriez; au lieu de cela, je les construirais comme des structures avec le moins de frais généraux possible et j'essaierais peut-être même de faire en sorte que la plupart des fonctions qui les utilisent (à l'exception de choses comme operator + qui ne peuvent pas vraiment être aidées) soient globales plutôt que des méthodes sur le struct. Une optimisation précoce est généralement recommandée contre, et pour une bonne raison,

Steven Stadnicki
la source
1
-1 car: la classe et la structure n'ont que des implications d'octroi d'accès en C ++ et l'OP n'a pas spécifié de langage de toute façon, les fonctions membres non virtuelles ont les mêmes implications en termes de performances que les fonctions non membres et les fonctions membres virtuelles (qui présentent potentiellement les problèmes qui vous préoccupent) n'existent que si vous les créez, pas simplement en utilisant l'héritage.
2
@JoshPetrie Points valides sur tous les fronts; J'ai tendance à «par défaut» en C / C ++ et j'ai donc vu la question à travers cet objectif. Je ne crois qu'il ya des performances légitimes (ainsi que conceptuel) raisons de ne pas emprunter la voie d'héritage, vous l' esprit, mais j'aurais pu être beaucoup mieux sur les détails spécifiques de. Je vais essayer de revoir cela et voir si je peux donner une meilleure comptabilité.
Steven Stadnicki