2010-06-14 10 views
8

¿Alguien tiene experiencia en reducir la cantidad de código de la plantilla utilizando herencia?Reducción de la hinchazón de la plantilla con herencia

vacilo reescribir nuestros contenedores de esta manera:

class vectorBase 
{ 
    public: 
    int size(); 
    void clear(); 
    int m_size; 
    void *m_rawData; 
    //.... 
}; 

template< typename T > 
class vector : public vectorBase 
{ 
    void push_back(const T&); 
    //... 

}; 

que debería mantener el máximo rendimiento al tiempo que reduce el tiempo de compilación

También me pregunto por qué implementaciones stl no utiliza este enfoque

Gracias para sus retroalimentaciones

+0

Este código no funciona como está, ya que todos los miembros 'vectorBase' son' privados', por lo tanto inaccesibles desde clases derivadas. –

+1

Y falta ';' en todas partes pero tenemos el punto. +1 pregunta interesante. – ereOn

+0

sí, sé que este código no se compilará. no me importa;) No quiero pegar un stl contenedores enteros ... – benoitj

Respuesta

1

Creo que esto es una optimización prematura. En general, excepto en los sistemas integrados, el espacio en disco y la memoria son abundantes y baratos, por lo que no hay razón para intentar optimizar una pequeña cantidad de espacio de código. Manteniéndolo todo en el código de la plantilla, hace que sea más obvio lo que está sucediendo en lugar de usar la herencia, lo que complicaría las cosas.

Además, la mayoría de las aplicaciones no generarán cientos de instancias, y para cada T no se pueden utilizar todos los métodos, lo que reduce aún más la huella del código.

Solo si hubiera consideraciones de memoria extremadamente ajustadas (integradas) consideraría diferentes enfoques posibles (incluido el que usted presentó).

EDITAR: No estoy seguro de que haya una gran ganancia en un poco de cajas de contenedores estándar, ya que todavía necesitan una gran cantidad de código de plantilla. Para las clases internas que tienen solo una pequeña cantidad de código específico de la plantilla y mucha lógica común, esto definitivamente podría ayudar tanto al código generado como a la velocidad de compilación. Sospecho que no se usa a menudo porque es más complejo y los beneficios están restringidos a ciertos escenarios.

+1

Está hablando de reducir _compile time._ – Stephen

+2

@Stephen: ¿Qué es exactamente lo que no sucederá con este enfoque? La mayor parte de la complejidad en un contenedor es llamar constructores y destructores en el momento apropiado, y esto solo se puede hacer en el código de plantilla donde el tipo contenido está completamente definido. –

+0

@Ben voigt: con este enfoque, las llamadas de ctor/dtor en el lugar están, por supuesto, en el código de la plantilla. Pero puedo poner en la clase base la asignación/destrucción de memoria para ejemplo – benoitj

0

IIRC, Qt usa (o usa?) Un enfoque similar para su QList et al.

Básicamente, funcionaría, pero debes asegurarte de poner todo lo que dependa de T dentro de la plantilla vectorial. Desafortunadamente, eso es casi todo el código que hay en la clase vectorial (en muchos casos, el código necesita llamar a algún constructor o destructor T), excepto para asignar almacenamiento crudo y size()/capacity(). No estoy seguro de que valga la pena, así que vuelva a verificarlo.

Sin duda paga cuando puede abstraerse de algún parámetro de plantilla (por ejemplo, set<T>::iterator no necesita conocer el comparador del conjunto) o si puede generar una implementación completa para una clase grande de tipos (por ejemplo, con trivial copy-ctor y dtor).

0

El código que ha publicado es simplemente incorrecto. Si la clase que está almacenando en el vector tiene un destructor, ese destructor no se invocará, porque el compilador ha perdido toda la información sobre cuándo llamar al destructor mediante conversión a void*.

Para hacer esto correctamente, llamando al destructor correcto, necesita generar copias diferentes del código que cada uno llama al destructor correcto, y este trabajo se simplifica mediante el uso de plantillas.

(Para utilizar el enfoque con una clase base no molde, es necesario generar apenas tanto código máquina, pero que necesita para escribir un conjunto mucho más código C++ con la mano.)

Es por eso que este enfoque realmente no vale la pena

+1

Sí, sé que mi código es incorrecto. Eso no es realmente importante. Solo estoy mostrando la idea de "extraer" tanto como sea posible el código de plantilla en una clase base. Tienes razón para dtor/ctor, pero por ejemplo podría poner malloc/free en la clase base – benoitj

3

Solo unas pocas operaciones en un vector tienen sentido si no sabes de qué tipo son los elementos almacenados. Por ejemplo, el método clear() que agregó a su clase base necesita llamar a los destructores de los elementos eliminados del vector, por lo que necesita saber su tipo y necesita ser modelado.

Tampoco es mucho lo que se puede hacer con el a void *m_rawData sin conocer los tipos de cosas que contiene, básicamente todas las operaciones al menos deben conocer el tamaño del tipo almacenado. Lo único que se me ocurre es que puedes free() si sabes que no contiene elementos (si contiene elementos tienes que llamar a sus destructores). Asignar, configurar y acceder a todos los elementos no funciona si no sabes dónde comienzan y terminan los elementos individuales. Además, la implementación de todos los métodos sería mucho más limpia y simple si m_rawData fuera un T* correctamente tipado.

Un size() método en la clase base sólo funcionaría si su único trabajo es devolver una variable miembro m_size, pero un vector no necesariamente tienen que almacenar de forma explícita el tamaño (las implementaciones yo sepa no lo hacen). Probablemente podría implementar es para que el tamaño se almacena explícitamente, pero de nuevo size() probablemente no es un método que tarde mucho en compilarse, incluso si se trata de plantillas.

En conjunto, no creo que queden muchos métodos implementables en una clase base. La mayoría de las operaciones en un vector necesitan conocer los elementos almacenados en él.

0

El corto de él:

Sí, este enfoque [probablemente] trabajo en circunstancias limitadas y especializadas. No sospecho que std::vector (o el resto de STL) se encuentran entre esas circunstancias.

El largo de la misma:

Como otros han mencionado (y estoy de acuerdo), fuera de un sistema integrado, el código de la hinchazón no es un gran problema en un binario compilado.

Pero, muchos de nosotros sufrimos el costo de compilación de construir magnitudes más código durante el paso de compilación de lo que podríamos hacerlo si hubiera bibliotecas compiladas para enlazar (en lugar de compilar archivos de encabezado). Añádele la dificultad de cambiar uno de esos archivos de cabecera con plantillas y ver todo el proyecto recompilar desde cero. De largo los tiempos de compilación hacen para los desarrolladores tristes :(

Puede que no afectan a un gran porcentaje de los desarrolladores -.., Dependiendo del tamaño de la base de código de su empresa, y cómo estructurar su entorno de construcción Ciertamente nos grava en mi empresa

Como han señalado algunas respuestas, no hay mucho para abstraer de un std::vector que lo haría justo mejor en su ejemplo. Ciertamente debe ser capaz de crear y destruir elementos individuales, y hacer cualquier método virtual obstaculizaría tiempo de ejecución (que es más importante que el rendimiento en tiempo de compilación). Además, el compilador perderá la capacidad de optimizar la biblioteca void* para el código de plantilla, esto podría resultar en una gran e pérdida de rendimiento.

Estoy más interesado en las estructuras más complejas - ¿se beneficiaría std::map de la abstracción? ¿Qué sucede si saca el árbol rojo-negro (la implementación de SGI) y lo vincula con una biblioteca de árbol rojo-negro?Debería (probablemente) almacenar los elementos fuera del std::map, por lo que no es necesario llamar a los destructores, pero eso podría (nuevamente) causar una pérdida de rendimiento en tiempo de ejecución debido a duplicar su indirección.

Estoy bastante seguro de que no se podía utilizar este método para mejorar la implementación de STL.

Si tuviera un mejor conocimiento de las estructuras de datos que almacenaban, o que tenía un tipo muy específico de plantilla (no necesariamente un contenedor), que probablemente podría mejorar su rendimiento. Pero, entonces, la pregunta es: ¿con qué frecuencia usará este nuevo tipo de plantilla para que la sobrecarga de la compilación se mejore notablemente? Ciertamente, ayudaría a compilar tiempos para std::vector, pero quizás no para my_domain_specific_ptr_container. Si ahorra el 50% del tiempo de compilación para my_domain_specific_ptr_container, ¿cuántas veces tendría que usarlo para notar un aumento de compilación lo suficientemente significativo como para justificar la complejidad agregada a la clase (y la capacidad de depuración reducida).

Si no lo ha hecho, su tiempo puede ser mejor gastado en la distribución de sus herramientas de construcción :)

Si, por el contrario, se intenta esto y que funciona para contenedores STL ... Por favor después de vuelta. ¡Quiero una compilación más rápida! ;)

1

Entiendo su enfoque.

Para ser sincero, lo he usado ... aunque obviamente no para contenedores STL: su código está virtualmente libre de fallas y optimizado, ¡y es muy poco probable que logre una mejor implementación por mi cuenta!

No me importa mucho el tiempo de compilación: es un problema vergonzosamente paralelo (aparte del enlace) y distcc etc. se encargan de todos los problemas que pueda tener incluso con una gran base de código. Y me refiero a grande, trabajo en una compañía que requería un nuevo compilador de HP porque la versión que teníamos no soportaba más de 128Ko ... en la línea de comando del enlazador. Y fue solo una de las aplicaciones, y fue hace unos años, y afortunadamente la dividieron en varios pedazos desde entonces.

Sin embargo, por mucho que no me preocupe en tiempo de compilación, I do se preocupan mucho por la reducción de las dependencias y la compatibilidad binaria. Y así, cuando escribo mi propio código de plantilla, comprobo si es posible factorizar algunas operaciones fuera del código de la plantilla.

La primera tarea es aislar los puntos donde realmente se puede ganar. Obtener una línea de código no vale la pena, desea obtener funciones completas.

La segunda tarea es decidir si desea o no para mantenerlos inline. Depende de si le preocupa el rendimiento o no, la sobrecarga de una llamada a función puede o no ser importante para usted.

sin embargo yo ciertamente no utilizar la herencia para el trabajo. Inheritance es una relación IS-A: define una interfaz, no una implementación. O use Composition o simplemente funciones gratuitas que guarde en un espacio de nombre de utilidad (detail como en Boost?).

0

Algunas implementaciones hacen usan (una forma de) el enfoque anterior. Aquí es de GCC

template<typename _Tp, typename _Alloc = std::allocator<_Tp> > 
    class vector : protected _Vector_base<_Tp, _Alloc> 
    { 
    ... 
    } 

En este caso, el objetivo es delegar la gestión de memoria para _Vector_base. Si elige dedicar su tiempo a reinventar STL, siga aquí con sus resultados.Quizás sus esfuerzos ayuden a poner fin a los gritos de "hinchazón de código" que todavía se escuchan de vez en cuando.

Cuestiones relacionadas