plantillas - programacion ats c++
Múltiples definiciones de una plantilla de función (3)
Supongamos que un archivo de encabezado define una plantilla de función. Ahora supongamos que dos archivos de implementación #include
este encabezado y cada uno de ellos tiene una llamada a la plantilla de función. En ambos archivos de implementación, la plantilla de función se instancia con el mismo tipo.
// header.hh
template <typename T>
void f(const T& o)
{
// ...
}
// impl1.cc
#include "header.hh"
void fimpl1()
{
f(42);
}
// impl2.cc
#include "header.hh"
void fimpl2()
{
f(24);
}
Uno puede esperar que el enlazador se queje de las definiciones múltiples de f()
. Específicamente, si f()
no sería una plantilla, ese sería el caso.
- ¿Cómo es que el enlazador no se queja de las múltiples definiciones de
f()
? - ¿Se especifica en el estándar que el vinculador debe manejar esta situación con elegancia? En otras palabras, ¿puedo contar siempre con programas similares a los anteriores para compilar y vincular?
- Si el enlazador puede ser lo suficientemente astuto como para eliminar la ambigüedad de un conjunto de instancias de plantillas de funciones, ¿por qué no puede hacer lo mismo para las funciones normales, dado que son idénticas como en el caso de las plantillas de funciones instanciadas?
El manual del compilador Gnu C ++ tiene una buena discusión de esto . Un experto:
Las plantillas de C ++ son la primera característica del lenguaje que requiere más inteligencia del entorno que la que normalmente se encuentra en un sistema UNIX. De alguna manera, el compilador y el enlazador deben asegurarse de que cada instancia de plantilla ocurra exactamente una vez en el ejecutable si es necesario, y de ninguna otra manera. Hay dos enfoques básicos para este problema, que se conocen como el modelo de Borland y el modelo de Cfront.
Modelo Borland
Borland C ++ resolvió el problema de creación de instancias de plantilla agregando el código equivalente de bloques comunes a su enlazador; el compilador emite instancias de plantilla en cada unidad de traducción que los usa, y el enlazador los colapsa. La ventaja de este modelo es que el vinculador solo debe considerar los archivos del objeto en sí; no hay complejidad externa de qué preocuparse. Esta desventaja es que el tiempo de compilación aumenta porque el código de la plantilla se está compilando repetidamente. El código escrito para este modelo tiende a incluir definiciones de todas las plantillas en el archivo de encabezado, ya que se debe ver que se crean instancias.
Modelo de Cfront
El traductor de AT & T C ++, Cfront, resolvió el problema de creación de instancias de plantilla al crear la noción de un repositorio de plantillas, un lugar mantenido automáticamente donde se almacenan instancias de plantillas. Una versión más moderna del repositorio funciona de la siguiente manera: a medida que se crean archivos de objetos individuales, el compilador coloca las definiciones de plantilla y las instancias encontradas en el repositorio. En el momento del enlace, el contenedor de enlace agrega en los objetos en el repositorio y compila las instancias necesarias que no fueron emitidas previamente. Las ventajas de este modelo son una velocidad de compilación más óptima y la capacidad de utilizar el enlazador del sistema; para implementar el modelo de Borland, un proveedor de compiladores también necesita reemplazar el enlazador. Las desventajas son una complejidad enormemente aumentada y, por lo tanto, un potencial de error; para algunos códigos esto puede ser igual de transparente, pero en la práctica puede ser muy difícil construir múltiples programas en un directorio y un programa en múltiples directorios. El código escrito para este modelo tiende a separar las definiciones de plantillas de miembro no en línea en un archivo separado, que se debe compilar por separado.
Cuando se usa con GNU ld versión 2.8 o posterior en un sistema ELF como GNU / Linux o Solaris 2, o en Microsoft Windows, G ++ admite el modelo Borland. En otros sistemas, G ++ no implementa ningún modelo automático.
Para admitir C ++, el enlazador es lo suficientemente inteligente como para reconocer que todas son la misma función y arroja todas menos una.
EDITAR: aclaración: el enlazador no compara los contenidos de la función y determina que son los mismos. Las funciones con plantilla se marcan como tales y el vinculador reconoce que tienen las mismas firmas.
Este es más o menos un caso especial solo para plantillas.
El compilador solo genera las instancias de plantilla que realmente se usan. Como no tiene control sobre qué código se generará a partir de otros archivos fuente, tiene que generar el código de la plantilla una vez para cada archivo, para asegurarse de que el método se genere en absoluto.
Como es difícil resolver esto (el estándar tiene una palabra clave extern
para plantillas, pero g ++ no lo implementa), el vinculador simplemente acepta las múltiples definiciones.