¿Debe Vector3 heredar de Vector2?

18

Estoy creando un par de clases Vector2(X e Y) y Vector3(X, Y y Z), pero no sé si hacer Vector3heredar Vector2o si volver a implementar las variables miembro m_xy m_yotra vez. ¿Cuáles son los pros y los contras de cada lado (herencia vs redefinición)?

Editar: estoy usando C ++ (VS2010).

Mark Ingram
fuente
1
¿Por qué no escribir una clase de vector general para n vectores dimensionales y luego (si es necesario) heredar una clase vector2 y una clase vector3? También puede usar plantillas para la clase general y también heredar versiones para vectores enteros y vectores flotantes. Editar: ¿Por qué no usas una biblioteca matemática optimizada?
danijar
55
De ninguna de las maneras "Vector3 es un Vector2", ambos podían heredar de una matriz VectorN aunque
wim
1
Sí, pero probablemente necesitará una tabla virtual y ese es uno de los casos en los que los costos de tiempo de ejecución y memoria pueden ser importantes. Idealmente, a Vector3debería ser solo 3 floatsen lo que respecta a la memoria. No digo que sea imposible, solo que nunca he visto eso en un motor de producción.
Laurent Couvidou
2
Sí, eso creo. Hasta que no necesites nada más que eso floats. Ya sabes, YAGNI, KISS, todas esas cosas. Vector2, Vector3y Vector4sin herencia y floatssolo es realmente el estándar de facto en los motores de juego.
Laurent Couvidou
1
Espero que hayas querido decir typedef float real;;).
Mark Ingram

Respuestas:

47

No, no debería. Lo único que usaría de la herencia son los componentes xy y. Los métodos utilizados en una Vector2clase no serían útiles en una Vector3clase, probablemente tomarían diferentes argumentos y realizarían operaciones en un número diferente de variables miembro.

MichaelHouse
fuente
+1, debería prestar más atención a la ventana emergente para no escribir cosas redundantes.
Matsemann
8
Herencia clásica sobreuso . Un Vector3IS-NOT-A Vector2(por lo que no debe heredar), pero un AppleIS-A Fruit(por lo que puede heredar). Si tuerce su mente lo suficiente, tiene un Vector3HAS-A Vector2, pero la pérdida de rendimiento y la codificación de dificultad significa que escribirá clases completamente separadas para Vector3y Vector2.
bobobobo
Pero podría (y en mi opinión debería) escribir una clase de vector n-dimensional para heredar un vector 2d y un vector 3d a partir de eso.
danijar
8

Hay algo curioso que puede hacer con C ++ (no especificó un idioma, y ​​esta respuesta se debe principalmente a que creo que es bueno ver alternativas, aunque realmente no creo que esto sea útil en la mayoría de los casos).

Usando plantillas puedes hacer algo como esto:

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

y esto se puede usar así:

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

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

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

Como dije, no creo que esto sea útil, y probablemente complicará tu vida cuando trates de implementar puntos / normalizar / otras cosas y trates de ser genérico con cualquier número de vectores.

Luke B.
fuente
Sí, todo el carácter genérico parece agradable, solamente la mayoría de las veces sólo necesita un 3-vector de serie con 3 componentes de punto flotante - todos los soportes de ángulo hará Vector3f vun poco más bloatyVector3<float> v
bobobobo
@bobobobo Sí, estoy de acuerdo. Mis clases de vectores generalmente son vec2 y vec3 sin padre, pero aún así las hago plantillas. Si escribir Vector3 <float> te molesta, siempre puedes escribirlo
Luke B.
... Y ahora el argumento del programador en C ... "pero ¿qué pasa con el mayor tiempo de compilación para usar plantillas?" ¿Realmente vale la pena en este caso?
bobobobo
@bobobobo Nunca tuve ningún problema con mis tiempos de compilación: P, pero nunca trabajé en un proyecto en el que el tiempo de compilación sería un problema. Se podría argumentar que los tiempos de compilación justifican no usar flotantes cuando se necesitan números enteros.
Luke B.
@bobobobo Con instancias explícitas y sin incluir su archivo en línea en el encabezado, los tiempos de compilación no serán diferentes. Además, la hinchazón del soporte angular de la plantilla está a solo una de typedefdistancia.
Samaursa
7

Independientemente de la velocidad, la primera pregunta que debe hacerse al hacer una herencia es si los va a usar polimórficamente. Más específicamente, ¿hay alguna situación en la que puedas verte usando un Vector3como si fuera un Vector2(que, al heredar de él, estás diciendo explícitamente que un Vector3 "es-un" Vector2).

Si no, entonces no deberías usar la herencia. No deberías estar usando la herencia para compartir código. Para eso están los componentes y las funciones externas, no es que compartirías ningún código entre ellos de todos modos.

Dicho esto, es posible que desee formas fáciles de convertir Vector3 s a Vector2s, y en ese caso puede escribir una sobrecarga del operador que truncará implícitamente Vector3a a Vector2. Pero no deberías heredar.

Tétrada
fuente
Gracias, creo que eso resaltó el problema, estaba mirando esto desde el punto de vista de "compartir código" (es decir, no tener que "volver a escribir" los valores X e Y).
Mark Ingram
+1 gran respuesta, no hay uso polimórfico entre vectores de diferentes tamaños.
Luke B.
Esto es lo más importante que iba a agregar a mi propia respuesta: +1 seguro. (Aunque existen circunstancias extrañas en las que me puedo imaginar querer polimorfismo, por ejemplo, juegos 2.5d 'heightmap' en los que cosas como verificaciones de distancia, rutas, etc., canónicamente querrán hacerse en 2d, pero aún debe proporcionar coordenadas 3d para los objetos)
Steven Stadnicki
@LukeB. Aunque en el caso de los OP, ¿estoy de acuerdo en que parece que no hay razón para heredar Vector2sino heredar de una base Vector<N>? Eso tiene mucho sentido. Además, ¿por qué la herencia significa automáticamente un comportamiento polimórfico? Una de las mejores cosas de C ++ es que puede tener una herencia de costo cero. No es necesario agregar ningún método virtual (incluidos los destructores virtuales) en la Vector<N>clase base .
Samaursa
5

No, dado que todos los métodos tendrán que ser anulados también, no tendrá que heredarlos.

Si algo, ambos podrían implementar una interfaz de Vector. Sin embargo, dado que probablemente no desee agregar / sub / dot / dst entre un Vector2 y un Vector3, esto tendrá efectos secundarios no deseados. Y tener diferentes parámetros, etc. sería una molestia.
Así que realmente no puedo ver ninguna ventaja de la herencia / interfaz en este caso.

Un ejemplo es el marco Libgdx, donde Vector2 y Vector3 no tienen nada que ver entre sí, aparte de tener el mismo tipo de métodos.

Matsemann
fuente
2

Si planea usar las matrices SIMD, es probable que sean las mejores. Si aún desea utilizar la sobrecarga del operador, puede considerar usar una interfaz / mixin para acceder a la matriz subyacente; por ejemplo, aquí hay un punto de partida que solo tiene el (no probado) Add.

Observe que no he proporcionado X/ Y/ Z, cada VectorXclase heredaría directamente de esta, por las mismas razones especificadas por otras personas. Aún así, he visto matrices utilizadas como vectores muchas veces en la naturaleza.

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

Descargo de responsabilidad: mi C ++ podría apestar, ha pasado un tiempo desde que lo usé.

Jonathan Dickinson
fuente
Espera , ¿tu uso de _aligned_mallocsignifica que el error que abrí no es realmente un error?
bobobobo
No debe usar conversiones de puntero para obtener sus valores en el __m128registro, debe usar _mm_loadu_psen su lugar. Una buena clase de muestra está aquí en "vectorclass.zip"
bobobobo
@bobobobo Haré el mejor intento en una edición: preste especial atención al descargo de responsabilidad;).
Jonathan Dickinson
@bobobobo _mm_loadu_psdebería funcionar para usted con esa estructura (donde _mm_load_psno lo hará). También agregué tu sugerencia: siéntete libre de editar la pregunta si crees que estoy ladrando el árbol equivocado (ha pasado un tiempo desde que usé C [++]).
Jonathan Dickinson
1

Otra desventaja seria de que Vec3 herede de Vec2 o, posiblemente, de que ambos hereden de una sola clase de Vector: su código hará muchode operaciones en vectores, a menudo en situaciones de tiempo crítico, y le conviene asegurarse de que todas esas operaciones sean tan rápidas como sea posible, mucho más de lo que es para muchos otros objetos que no son bastante universal o de bajo nivel. Si bien un buen compilador hará todo lo posible para aplanar cualquier sobrecarga de herencia, todavía confía más en el compilador allí de lo que le gustaría; en cambio, los construiría como estructuras con la menor sobrecarga posible y posiblemente incluso trataría de hacer que la mayoría de las funciones que las usan (con la excepción de cosas como operador + que realmente no se pueden evitar) sean globales en lugar de métodos en el estructura La optimización temprana generalmente se recomienda contra, y con una excelente razón,

Steven Stadnicki
fuente
1
-1 porque: class y struct solo tienen implicaciones de concesión de acceso en C ++ y, de todos modos, el OP no especificó un lenguaje, las funciones miembro no virtuales tienen las mismas implicaciones de rendimiento que las funciones no miembro y las funciones miembro virtuales (que potencialmente exhiben los problemas que le preocupan) solo existen si los hace, no simplemente utilizando la herencia.
2
@JoshPetrie Puntos válidos en todos los frentes; Tiendo a 'predeterminado' a C / C ++ y por eso vi la pregunta a través de ese lente. Yo no creo que hay un rendimiento legítimo (así como conceptuales) razones para no tomar la ruta de la herencia, le importaría, pero podría haber sido mucho mejor en los detalles específicos. Intentaré revisar esto y ver si puedo dar una mejor contabilidad.
Steven Stadnicki