2009-04-07 14 views
8

Teniendo en cuenta los siguientes dos escenarios de uso (exactamente como los ves, es decir, el usuario final sólo estará interesado en utilizar Vector2_t y Vector3_t):Herencia frente a la especialización

[1] Herencia:

template<typename T, size_t N> struct VectorBase 
{ 
}; 

template<typename T> struct Vector2 : VectorBase<T, 2> 
{ 
}; 

template<typename T> struct Vector3 : VectorBase<T, 3> 
{ 
}; 

typedef Vector2<float> Vector2_t; 
typedef Vector3<float> Vector3_t; 

[2] Estructura del máster:

template<typename T, size_t N> struct Vector 
{ 
}; 

template<typename T> struct Vector<T, 2> 
{ 
}; 

template<typename T> struct Vector<T, 3> 
{ 
}; 

typedef Vector<float, 2> Vector2_t; 
typedef Vector<float, 3> Vector3_t; 

no puedo hacer mi mente en cuanto a que es una solución mejor. La ventaja obvia de la herencia es la reutilización del código en las clases derivadas; una posible desventaja es el rendimiento (mayor tamaño, los usuarios pueden pasar por valor, etc.). La especialización parece evitar todo eso, pero a costa de tener que repetirme muchas veces.

¿Qué otras ventajas/desventajas extrañé, y en su opinión, qué ruta debo tomar?

Respuesta

12

Lo que se desea en última instancia, creo, es que el tipo de usuario

Vector<T, N> 

Y dependiendo de N, el usuario obtendrá ligeras cosas diferentes. El primero no cumplirá eso, pero el segundo lo hará, sobre el precio de la duplicación de código.

Lo que puede hacer es invertir la herencia:

template<typename T, size_t N> struct VectorBase 
{ 
}; 

template<typename T> struct VectorBase<T, 2> 
{ 
}; 

template<typename T> struct VectorBase<T, 3> 
{ 
}; 

template<typename T, size_t N> struct Vector : VectorBase<T, N> 
{ 
}; 

y poner en práctica las pocas funciones que dependen solamente de N es un valor específico en la clase base apropiada. Puede agregar un destructor protegido en ellos, para evitar que los usuarios eliminen instancias de Vector mediante punteros al VectorBase (normalmente ni siquiera deberían poder nombrar VectorBase: Ponga esas bases en algún espacio de nombres de implementación, como detail).

Otra idea es combinar esta solución con la que se menciona en otra respuesta. Heredar de forma privada (en lugar de públicamente como arriba) y agregar funciones de envoltura en la clase derivada que llama a las implementaciones de la clase base.

Sin embargo, otra idea es utilizar una sola clase y luego enable_if (usando boost::enable_if) para activar o desactivar para valores particulares de N, o utilizar un transformador int-a-tipo como éste, que es mucho más simple

struct anyi { }; 
template<size_t N> struct i2t : anyi { }; 

template<typename T, size_t N> struct Vector 
{ 
    // forward to the "real" function 
    void some_special_function() { some_special_function(i2t<N>()); } 

private: 
    // case for N == 2 
    void some_special_function(i2t<2>) { 
     ... 
    } 

    // case for N == 3 
    void some_special_function(i2t<3>) { 
     ... 
    } 

    // general case 
    void some_special_function(anyi) { 
     ... 
    } 
}; 

De esta manera, es completamente transparente para el usuario de Vector. Tampoco agregará ningún espacio adicional para los compiladores que realizan la optimización de la clase base vacía (bastante común).

+0

La respuesta más completa. ¡Gracias! –

+0

respuesta agradable y FRÍO. Toma un gurú para obtener una respuesta tan elegante. –

+0

+1. Sospecho que su sugerencia de enable_if <> en una sola plantilla de clase es la mejor: seguramente las únicas diferencias entre las clases de plantilla para diferentes dimensiones de vector serían la cantidad de parámetros en el constructor. –

0

Si está utilizando demasiado la especialización de plantillas, probablemente necesite replantear su diseño. Teniendo en cuenta que lo escondes detrás de un typedef, dudo que lo necesites.

4

Usa la herencia y la herencia privada. Y no use ninguna función virtual. Dado que con la herencia privada, no tiene is-a, nadie podrá usar un puntero de baseclas para una subclase derivada, y no obtendrá el problema de corte al pasar por valor.

Esto le ofrece lo mejor de ambos mundos (y de hecho es así como la mayoría de las bibliotecas implementan muchas de las clases de STL).

De http://www.hackcraft.net/cpp/templateInheritance/ (discutiendo std :: vector, no su clase ector V):

vector<T*> se declara tener una base privada de vector<void*> . Todas las funciones que colocan un nuevo elemento en el vector, tales como push_back(), llaman a la función equivalente en esta base privada, por lo internamente nuestra vector<T*> utiliza una vector<void*> para su almacenamiento. Todas las funciones que devuelven un elemento del vector, como front(), realizan un static_cast en el resultado de llamar a la función equivalente en la base privada. Dado que la única manera de obtener un puntero en el vector<void*> (aparte de trucos deliberadamente peligrosas) es a través de la interfaz ofrecido por vector<T*> es seguro a staticly tire la void* de nuevo a T* (o el void*& de nuevo a T*&, y pronto).

En general, si el STL lo hace así, parece un modelo decente para emular.

+0

Ese es un buen punto, me olvidé por completo de la STL. –

+0

Un nit-pick: Estrictamente hablando, sí tiene "is a", pero ese "is a" no es público. P.ej. a 'std: vector ' es un 'std: vector ' como un medio para implementar cosas con una buena eficiencia de espacio. Pero ese "es un" ser privado, en lo que respecta a cualquier código externo, 'std: vectory ' no es un 'std: vectory ' porque no puede "ver" la relación. Así que estrictamente tienes is-a, pero en la práctica no lo haces :) –

0

La herencia solo debe usarse para modelar "is-a". La especialización sería la alternativa más limpia. Si necesita o desea usar herencia por cualquier razón, al menos conviértala en herencia privada o protegida, de modo que no herede públicamente de una clase con un destructor público no virtual.

Sí, los chicos plantilla metaprogramación siempre lo hacen

struct something : something_else {}; 

Pero esos something s son metafunciones y no destinados a ser utilizados como tipos.

Cuestiones relacionadas