Design für vector-Klasse gesucht



  • Hallo!

    Ich bin gerade dabei, mir ein paar Hilfsklassen für ein Projekt zu schreiben. Unter anderen schreibe ich mir gerade Klassen wie Vector2D, Vector3D, Vector4D, die jeweils eine Koordinate aus 2, 3 oder 4 Komponenten speichern. Da jede dieser Klassen im Prinzip die selben Funktionen besitzt und sich lediglich durch die Anzahl der Komponenten unterscheidet, frage ich mich gerade, ob es nicht eine elegante Möglichkeit gibt, das alles nur einmal zu schreiben.

    Über Templates wäre ja etwas in der Art hier möglich:

    template<T,E> class vector {
       T data[E];
    }
    

    womit ich die einzelnen Elemente z.B. via my_vector.data[0] ansprechen könnte. Allerdings möchte ich nur ungern darauf verzichten, Elemente auch über ihren Namen ansprechen zu können. (also z.B. my_vector.x)
    Gibt es dafür eine elegante Möglichkeit (ggf auch mit C++11)?

    Danke für eure Hilfe



  • Du kannst ja Enums definieren:

    enum Achse {x, y, z, usw.};
    

    #define a.x (a.data[x])
    #define a.y (a.data[y])
    //Usw...
    //Oder dann gleich
    #define a.x (a.data[0])
    

    DAnn könntest du theoretisch my_vector.x schreiben
    (bin mir nicht zu 100 prozent sicher ob das so einfach geht sry)

    Aber so ind die richtung müsste es eig gehen :p



  • Skybuildhero schrieb:

    Du kannst ja Enums definieren:

    enum Achse {x, y, z, usw.};
    

    das würde man dann natürlich mit einem unbenannten enum machen.

    Skybuildhero schrieb:

    #define a.x (a.data[x])
    #define a.y (a.data[y])
    //Usw...
    //Oder dann gleich
    #define a.x (a.data[0])
    

    DAnn könntest du theoretisch my_vector.x schreiben
    (bin mir nicht zu 100 prozent sicher ob das so einfach geht sry)

    Aber so ind die richtung müsste es eig gehen :p

    nope, das stimmt nicht.

    @te, so etwas in der art hab ich auch mal gewollt.

    wenn du vec.x = 1; schreiben willst, dann gibt es probleme:
    1. entweder x ist eine referenz was u.u. speicheroverhead und laufzeitoverhead sein kann.
    2. x ist direkt der member, dann hat der []-operator einen laufzeitoverhead.

    alternativen:

    1. x(vec) = 1;
    2. vec.x() = 1;
    3. vec.x(1);
      4. du verzichtest auf xyz (da man die beim programmieren eh nie braucht, schleifen etc die die vektoren verarbeiten greifen immer indice-weise auf die komponenten zu).

    hier mal etwas kleines in 5min hingewursteltes was mir spontan eingefallen ist: (ungetestet)

    template<typename T, unsigned n>
    struct VectorBase
    {
    };
    
    template<typename T>
    struct VectorBase<T, 1>
    {
    	T& X;
    
    protected:
    	VectorBase(T* Data)
    		: X(Data[0])
    	{
    	}
    };
    template<typename T>
    struct VectorBase<T, 2> : public VectorBase<T, 1>
    {
    	T& Y;
    
    protected:
    	VectorBase(T* Data)
    		: VectorBase<T, 1>(Data), Y(Data[1])
    	{
    	}
    };
    template<typename T>
    struct VectorBase<T, 3> : public VectorBase<T, 2>
    {
    	T& Z;
    
    protected:
    	VectorBase(T* Data)
    		: VectorBase<T, 2>(Data), Z(Data[2])
    	{
    	}
    };
    template<typename T>
    struct VectorBase<T, 4> : public VectorBase<T, 3>
    {
    	T& W;
    
    protected:
    	VectorBase(T* Data)
    		: VectorBase<T, 3>(Data), W(Data[3])
    	{
    	}
    };
    
    template<typename T, unsigned n>
    class Vector : public VectorBase<T, n>
    {
    public:
    	typedef T ValueType;
    	enum
    	{
    		Dimensions = n
    	};
    private:
    	ValueType MyData[Dimensions];
    	typedef VectorBase<T, n> MyBase;
    
    public:
    	Vector()
    		: MyBase(MyData)
    	{
    	}
    
    	ValueType const& operator[] (unsigned Index) const
    	{
    		return this->MyData[Index];
    	}
    	ValueType& operator[] (unsigned Index)
    	{
    		return const_cast<ValueType&>(static_cast<Vector const&>(*this)[Index]);
    	}
    };
    


  • template <typename Scalar, unsigned size>
    struct Vector
    {
      union
      {
        Scalar data[size];
        struct
        {
          Scalar x, y, z, w; // Whatever
        }
      };
    };
    
    // ...
    
    Vector<float, 3> myVec;
    myVec.x = 1.0f; // Geht
    myVec.y = 2.0f; // Geht
    myVec.z = myVec.x + myVec.y; // Geht
    myVec.w = 123.0f; // Geht!!
    

    Das wäre eine Möglichkeit, die aber einen Nachteil hat: Dein Vektor hat immer mindestens 4 Elemente.
    EDIT2: Diese Methode sollte generell vermieden werden, siehe asfdlols post weiter unten.

    Hier wäre eine ander Lösung:

    template <typename Scalar, unsigned size>
    struct Vector
    {
      Scalar data[size];
    
      inline Scalar &x(void) { return data[0]; }
      inline Scalar &y(void) { return data[1]; }
      inline Scalar &z(void) { return data[2]; }
      inline Scalar &w(void) { return data[3]; }
    };
    
    // ...
    
    Vector<float, 3> myVec;
    myVec.x() = 1.0f; // Geht
    myVec.y() = 2.0f; // Geht
    myVec.z() = myVec.x() + myVec.y(); // Geht
    myVec.w() = 123.0f; // Möglich, aber undefined behaviour
    

    Ich sehe beide Möglichkeiten als schlechte Lösung an.
    Selbst mach ich es immer ungefähr so:

    template <typename Scalar, unsigned size>
    struct Vector
    {
      Scalar data[size];
    
      Scalar &operator () (unsigned index)
      {
        if(index < 0 || >= size) throw 0xDEADBEEF; // throw nur als Beispiel
        return data[index];
      }
    };
    
    // ...
    
    Vector<float, 3> myVec;
    
    myVec(0) = 1.0f; // Geht
    myVec(1) = 2.0f; // Geht
    myVec(2) = myVec(0) + myVec(1); // Geht
    myVec(3) = 123.0f; // Runtime error (exception)
    

    Das ist faul geschriebenes C++, Codes könnten Fehler enthalten.

    EDIT: Im letzten Beispiel kannst du natürlich auch den [] Operator überladen.

    Lg


  • Mod

    uebersetzen wir das mal

    Reaktor schrieb:

    - Klassen wie Vector2D, Vector3D, Vector4D
    - jeweils eine Koordinate aus 2, 3 oder 4 Komponenten speichern
    - die selben Funktionen
    - Anzahl der Komponenten unterscheidet,

    in c++

    //Klassen wie Vector2D, Vector3D, Vector4D
    class Vector2D_
    {
      float x,y;
    public:
     float& At(size_t Idx){return Idx==1?y:x;}
    };
    class Vector3D_ : public Vector2D_
    {
     float z;
    public:
     float& At(size_t Idx){return Idx==2?z:Vector2D_::At(Idx);}
    };
    class Vector4D_ : public Vector3D_
    {
     float w;
    public:
     float& At(size_t Idx){return Idx==3?w:Vector3D_::At(Idx);}
    };
    
    //jeweils eine Koordinate aus 2, 3 oder 4 Komponenten speichern
    template <size_t Komponenten,class Vec>
    class VectorMath : public Vec
    {
    public:
    //die selben Funktionen z.b.
    float dot(const VectorMath<Komponenten,Vec>& rOther)const
    {
      float Sum=0.f;
      for(size_t a=0;a<Komponenten;a++)
        Sum+=At(a)*rOther.At(a);
      return Sum;
    }
    };
    

    das waere meine uebersetzung aus dem deutschen.
    zum benutzen:

    typedef VectorMath<2,Vector2D_> tdVector2D;//du kannst natuerlich die 2 als 
    typedef VectorMath<3,Vector3D_> tdVector3D;//constant in der klasse deklarieren, 
    typedef VectorMath<4,Vector4D_> tdVector4D;//falls du das als weniger 'magic' empfindest
    

    du kannst weiterhin auf x,y,z,w zugreifen falls du die public machst oder accessors implementierst, so wie du das magst. du hast eine implementierung der funktionalitaet.



  • Singender Holzkübel schrieb:

    template <typename Scalar, unsigned size>
    struct Vector
    {
      union
      {
        Scalar data[size];
        struct
        {
          Scalar x, y, z, w; // Whatever
        }
      };
    };
    
    // ...
    
    Vector<float, 3> myVec;
    myVec.x = 1.0f; // Geht
    myVec.y = 2.0f; // Geht
    myVec.z = myVec.x + myVec.y; // Geht
    myVec.w = 123.0f; // Geht!!
    

    Das wäre eine Möglichkeit, die aber einen Nachteil hat: Dein Vektor hat immer mindestens 4 Elemente.

    sie hat noch einen: undefined behaviour.



  • @rapso ist unter den Bedingungen überhaupt noch sichergestellt, dass das speicherlayout der vektoren irgendwas ist, womit die Grafikkarte klar kommt? Also dass die elemente kontinuirlich im Sepicher liegen und kein padding etc drin vorkommt?



  • asfdlol schrieb:

    sie hat noch einen: undefined behaviour.

    Wo genau?



  • Meine einfachen Matrix-/Vektorklassen nutzten nie Vererbung. Halte ich auch nicht fuer richtig. Zu Speicherlayout? Da gibt es vielleicht Unterschiede in Debug und Release.

    sie hat noch einen: undefined behaviour.

    Nun, normalerweise kann man von einen Union nur ueber den Member wieder lesen, ueber den man reingeschrieben hat. Die meisten Compiler verhalten sich trotzdem wie erwartet, wenn Arrayelemente auf Member gemappet werden, wenn das Speicherlayout gleich ist. Jedoch besitzen hier Scalar[3] und struct ... x,y,z,w nicht das gleiche Speicherlayout, was zu Problemen fuehren kann.

    Elemente auch über ihren Namen ansprechen zu können.

    Ich verzichte darauf, weils eben nicht noetig ist und du dir bei Bedarf einfach Zugriffsfunktionen schreiben kannst. Aus vec.x wird dann eben vec.x() .



  • Singender Holzkübel schrieb:

    asfdlol schrieb:

    sie hat noch einen: undefined behaviour.

    Wo genau?

    1. wenn man in einen member einer union schreibt darf man von den anderen nicht lesen. das erlaubt deine implementierung aber.
    2. ich bin mir nicht sicher ob das pod-struct und das array plattformunabhängig konsistent gemappt werden, bzw. weiss ich nicht ob es definiert ist vom standard.

    edit:

    knivil schrieb:

    Meine einfachen Matrix-/Vektorklassen nutzten nie Vererbung. Halte ich auch nicht fuer richtig. Zu Speicherlayout? Da gibt es vielleicht Unterschiede in Debug und Release.

    mit speicherlayout ist vermutlich gemeint, dass es bei gewissen apis vorteilhaft ist wenn die komponenten eines vektors fortlaufend gespeichert sind.

    edit 2:

    knivil schrieb:

    Elemente auch über ihren Namen ansprechen zu können.

    Ich verzichte darauf, weils eben nicht noetig ist und du dir bei Bedarf einfach Zugriffsfunktionen schreiben kannst. Aus vec.x wird dann eben vec.x() .

    👍 sehe ich genau so.



  • mit speicherlayout ist vermutlich gemeint, dass es bei gewissen apis vorteilhaft ist wenn die komponenten eines vektors fortlaufend gespeichert sind.

    Ich weiss.



  • asfdlol schrieb:

    1. wenn man in einen member einer union schreibt darf man von den anderen nicht lesen. das erlaubt deine implementierung aber.
    2. ich bin mir nicht sicher ob das pod-struct und das array plattformunabhängig konsistent gemappt werden, bzw. weiss ich nicht ob es definiert ist vom standard.

    Stimmt du hast recht, daran habe ich nicht gedacht.
    Original Post edited.


  • Mod

    otze schrieb:

    @rapso ist unter den Bedingungen überhaupt noch sichergestellt, dass das speicherlayout der vektoren irgendwas ist, womit die Grafikkarte klar kommt? Also dass die elemente kontinuirlich im Sepicher liegen und kein padding etc drin vorkommt?

    solange du da nicht explizit alignment/padding dranbaust, werden die klassen wie erwartet im speicher liegen. auch auf gpus (falls du gerade cuda meinst) wuerde es richtig laufen.



  • rapso schrieb:

    otze schrieb:

    @rapso ist unter den Bedingungen überhaupt noch sichergestellt, dass das speicherlayout der vektoren irgendwas ist, womit die Grafikkarte klar kommt? Also dass die elemente kontinuirlich im Sepicher liegen und kein padding etc drin vorkommt?

    solange du da nicht explizit alignment/padding dranbaust, werden die klassen wie erwartet im speicher liegen. auch auf gpus (falls du gerade cuda meinst) wuerde es richtig laufen.

    das dürfte für non-pod-types (wie deiner einer ist) implementation-defined sein.


  • Mod

    asfdlol schrieb:

    rapso schrieb:

    otze schrieb:

    @rapso ist unter den Bedingungen überhaupt noch sichergestellt, dass das speicherlayout der vektoren irgendwas ist, womit die Grafikkarte klar kommt? Also dass die elemente kontinuirlich im Sepicher liegen und kein padding etc drin vorkommt?

    solange du da nicht explizit alignment/padding dranbaust, werden die klassen wie erwartet im speicher liegen. auch auf gpus (falls du gerade cuda meinst) wuerde es richtig laufen.

    das dürfte für non-pod-types (wie deiner einer ist) implementation-defined sein.

    ich seh nicht wo etwas nicht POD sein soll.



  • rapso schrieb:

    asfdlol schrieb:

    rapso schrieb:

    otze schrieb:

    @rapso ist unter den Bedingungen überhaupt noch sichergestellt, dass das speicherlayout der vektoren irgendwas ist, womit die Grafikkarte klar kommt? Also dass die elemente kontinuirlich im Sepicher liegen und kein padding etc drin vorkommt?

    solange du da nicht explizit alignment/padding dranbaust, werden die klassen wie erwartet im speicher liegen. auch auf gpus (falls du gerade cuda meinst) wuerde es richtig laufen.

    das dürfte für non-pod-types (wie deiner einer ist) implementation-defined sein.

    ich seh nicht wo etwas nicht POD sein soll.

    ist nicht pod, da:
    1. vererbung
    2. member in private


  • Mod

    asfdlol schrieb:

    rapso schrieb:

    asfdlol schrieb:

    rapso schrieb:

    otze schrieb:

    @rapso ist unter den Bedingungen überhaupt noch sichergestellt, dass das speicherlayout der vektoren irgendwas ist, womit die Grafikkarte klar kommt? Also dass die elemente kontinuirlich im Sepicher liegen und kein padding etc drin vorkommt?

    solange du da nicht explizit alignment/padding dranbaust, werden die klassen wie erwartet im speicher liegen. auch auf gpus (falls du gerade cuda meinst) wuerde es richtig laufen.

    das dürfte für non-pod-types (wie deiner einer ist) implementation-defined sein.

    ich seh nicht wo etwas nicht POD sein soll.

    ist nicht pod, da:
    1. vererbung
    2. member in private

    fuers layouten der klassen sind es PODs, es gibt keine member oder sonst etwas was ein alignment bzw. padding enforcen wuerde auf nachfolgende elemente.
    solange du nicht per hand eingreifst ist das layout wohl definiert und wird von jedem c++ compiler gleich umgesetzt.



  • rapso schrieb:

    asfdlol schrieb:

    rapso schrieb:

    asfdlol schrieb:

    rapso schrieb:

    otze schrieb:

    @rapso ist unter den Bedingungen überhaupt noch sichergestellt, dass das speicherlayout der vektoren irgendwas ist, womit die Grafikkarte klar kommt? Also dass die elemente kontinuirlich im Sepicher liegen und kein padding etc drin vorkommt?

    solange du da nicht explizit alignment/padding dranbaust, werden die klassen wie erwartet im speicher liegen. auch auf gpus (falls du gerade cuda meinst) wuerde es richtig laufen.

    das dürfte für non-pod-types (wie deiner einer ist) implementation-defined sein.

    ich seh nicht wo etwas nicht POD sein soll.

    ist nicht pod, da:
    1. vererbung
    2. member in private

    fuers layouten der klassen sind es PODs, es gibt keine member oder sonst etwas was ein alignment bzw. padding enforcen wuerde auf nachfolgende elemente.
    solange du nicht per hand eingreifst ist das layout wohl definiert und wird von jedem c++ compiler gleich umgesetzt.

    das stimmt in meinen augen für vererbung nicht:

    N1905 §10.0.4 schrieb:

    The order in which the base class subobjects are allocated in the most derived object (1.8) is unspecified. [Note: a
    derived class and its base class subobjects can be represented by a directed acyclic graph (DAG) where an arrow means
    “directly derived from.” A DAG of subobjects is often referred to as a “subobject lattice.”
    Base

    Derived1

    Derived2
    5 The arrows need not have a physical representation in memory. — end note ]


  • Mod

    asfdlol schrieb:

    rapso schrieb:

    asfdlol schrieb:

    rapso schrieb:

    asfdlol schrieb:

    rapso schrieb:

    otze schrieb:

    @rapso ist unter den Bedingungen überhaupt noch sichergestellt, dass das speicherlayout der vektoren irgendwas ist, womit die Grafikkarte klar kommt? Also dass die elemente kontinuirlich im Sepicher liegen und kein padding etc drin vorkommt?

    solange du da nicht explizit alignment/padding dranbaust, werden die klassen wie erwartet im speicher liegen. auch auf gpus (falls du gerade cuda meinst) wuerde es richtig laufen.

    das dürfte für non-pod-types (wie deiner einer ist) implementation-defined sein.

    ich seh nicht wo etwas nicht POD sein soll.

    ist nicht pod, da:
    1. vererbung
    2. member in private

    fuers layouten der klassen sind es PODs, es gibt keine member oder sonst etwas was ein alignment bzw. padding enforcen wuerde auf nachfolgende elemente.
    solange du nicht per hand eingreifst ist das layout wohl definiert und wird von jedem c++ compiler gleich umgesetzt.

    das stimmt in meinen augen für vererbung nicht:

    N1905 §10.0.4 schrieb:

    The order in which the base class subobjects are allocated in the most derived object (1.8) is unspecified. [Note: a
    derived class and its base class subobjects can be represented by a directed acyclic graph (DAG) where an arrow means
    “directly derived from.” A DAG of subobjects is often referred to as a “subobject lattice.”
    Base

    Derived1

    Derived2
    5 The arrows need not have a physical representation in memory. — end note ]

    das ist aber ein POD fuers layout.

    A type that is standard-layout means that it orders and packs its members in a way that is compatible with C. A class or struct is standard-layout, by definition, provided:
    It has no virtual functions
    It has no virtual base classes
    All its non-static data members have the same access control (public, private, protected)
    All its non-static data members, including any in its base classes, are in the same one class in the hierarchy
    The above rules also apply to all the base classes and to all non-static data members in the class hierarchy
    It has no base classes of the same type as the first defined non-static data member

    A class/struct/union is considered POD if it is trivial, standard-layout, and all of its non-static data members and base classes are PODs.



  • rapso schrieb:

    asfdlol schrieb:

    rapso schrieb:

    asfdlol schrieb:

    rapso schrieb:

    asfdlol schrieb:

    rapso schrieb:

    otze schrieb:

    @rapso ist unter den Bedingungen überhaupt noch sichergestellt, dass das speicherlayout der vektoren irgendwas ist, womit die Grafikkarte klar kommt? Also dass die elemente kontinuirlich im Sepicher liegen und kein padding etc drin vorkommt?

    solange du da nicht explizit alignment/padding dranbaust, werden die klassen wie erwartet im speicher liegen. auch auf gpus (falls du gerade cuda meinst) wuerde es richtig laufen.

    das dürfte für non-pod-types (wie deiner einer ist) implementation-defined sein.

    ich seh nicht wo etwas nicht POD sein soll.

    ist nicht pod, da:
    1. vererbung
    2. member in private

    fuers layouten der klassen sind es PODs, es gibt keine member oder sonst etwas was ein alignment bzw. padding enforcen wuerde auf nachfolgende elemente.
    solange du nicht per hand eingreifst ist das layout wohl definiert und wird von jedem c++ compiler gleich umgesetzt.

    das stimmt in meinen augen für vererbung nicht:

    N1905 §10.0.4 schrieb:

    The order in which the base class subobjects are allocated in the most derived object (1.8) is unspecified. [Note: a
    derived class and its base class subobjects can be represented by a directed acyclic graph (DAG) where an arrow means
    “directly derived from.” A DAG of subobjects is often referred to as a “subobject lattice.”
    Base

    Derived1

    Derived2
    5 The arrows need not have a physical representation in memory. — end note ]

    das ist aber ein POD fuers layout.

    A type that is standard-layout means that it orders and packs its members in a way that is compatible with C. A class or struct is standard-layout, by definition, provided:
    It has no virtual functions
    It has no virtual base classes
    All its non-static data members have the same access control (public, private, protected)
    All its non-static data members, including any in its base classes, are in the same one class in the hierarchy
    The above rules also apply to all the base classes and to all non-static data members in the class hierarchy
    It has no base classes of the same type as the first defined non-static data member

    A class/struct/union is considered POD if it is trivial, standard-layout, and all of its non-static data members and base classes are PODs.

    der von mir gequotete paragraph schliesst layout-pods nicht aus und deiner macht keine aussage bezüglich der anordnung - von im beispiel x, y, z und w - in der most-derived-class.


Log in to reply