proceso funciona compilar compilador compilacion como c++ visual-studio templates gcc compiler-construction

c++ - funciona - Detalles de instanciación de plantilla de compiladores GCC y MS



mingw (2)

¿Podría alguien proporcionar una comparación o detalles específicos de cómo se maneja la instanciación de la plantilla en tiempo de compilación y / o enlace en los compiladores GCC y MS? ¿Es este proceso diferente en el contexto de bibliotecas estáticas, bibliotecas compartidas y ejecutables? Encontré este documento sobre cómo lo maneja GCC, pero no estoy seguro de si la información aún se refiere al estado actual de las cosas. ¿Debo usar los indicadores que sugieren al compilar mis bibliotecas, por ejemplo, -fno-implicit-templates ?

Lo que sé (podría no ser necesariamente correcto) es que:

  • las plantillas se instanciarán cuando realmente se usen
  • las plantillas se instanciarán como resultado de instancias explícitas
  • la creación de instancias duplicadas generalmente se maneja al plegar instancias duplicadas o aplazando la creación de instancias hasta el momento del enlace

Punto de ejemplificación

las plantillas se instanciarán cuando realmente se usen

No exactamente, pero aproximadamente. El punto preciso de creación de instancias es un poco sutil, y te delego en la sección llamada Point of instanciaiation en el excelente libro de Vandevoorde / Josuttis.

Sin embargo, los compiladores no implementan necesariamente los POI correctamente: error c ++ / 41995: Punto de instanciación incorrecto para la plantilla de función

Instanciación parcial

las plantillas se instanciarán cuando realmente se usen

Eso es parcialmente correcto. Es cierto para las plantillas de funciones, pero para las plantillas de clase, solo se crean instancias de las funciones miembro que se utilizan. El siguiente es un código bien formado:

#include <iostream> template <typename> struct Foo { void let_me_stay() { this->is->valid->code. get->off->my->lawn; } void fun() { std::cout << "fun()" << std::endl; } }; int main () { Foo<void> foo; foo.fun(); }

let_me_stay() se comprueba sintácticamente (y la sintaxis es correcta), pero no semánticamente (es decir, no se interpreta).


Búsqueda de dos fases

Sin embargo, solo el código dependiente se interpreta más adelante; claramente, dentro de Foo<> , this depende de la plantilla id exacta con la que Foo<> se instancia, por lo que Foo<>::let_me_alone() comprobación de errores de Foo<>::let_me_alone() hasta el momento de la instanciación.

Pero si no usamos algo que dependa de la instanciación específica, el código debe ser bueno. Por lo tanto, lo siguiente no está bien formado:

$ cat non-dependent.cc template <typename> struct Foo { void I_wont_compile() { Mine->is->valid->code. get->off->my->lawn; } }; int main () {} // note: no single instantiation

Mine es un símbolo completamente desconocido para el compilador, a diferencia de this , para el cual el compilador podría determinar su dependencia de instancias.

El punto clave aquí es que C ++ usa un modelo de two-phase-lookup , donde verifica el código no dependiente en la primera fase, y la verificación semántica para el código dependiente se realiza en la fase dos (y el tiempo de creación de instancias) (esto también es un concepto a menudo mal entendido o desconocido, muchos programadores de C ++ suponen que las plantillas no se analizan en absoluto hasta la creación de instancias, pero eso es solo un mito que proviene de ..., Microsoft C ++).


Ejemplificación completa de plantillas de clase

La definición de Foo<>::let_me_stay() funcionó porque la comprobación de errores se pospuso para más adelante, en cuanto a this puntero, que es dependiente. Excepto cuando hubieras hecho uso de

instancias explícitas

cat > foo.cc #include <iostream> template <typename> struct Foo { void let_me_stay() { this->is->valid->code. get->off->my->lawn; } void fun() { std::cout << "fun()" << std::endl; } }; template struct Foo<void>; int main () { Foo<void> foo; foo.fun(); } g++ foo.cc error: error: ‘struct Foo<void>’ has no member named ‘is’


Definiciones de plantilla en diferentes unidades de traducción

Cuando crea una instancia explícita, crea instancias explícitamente. Y haga que todos los símbolos sean visibles para el vinculador, lo que también significa que la definición de la plantilla puede residir en diferentes unidades de traducción:

$ cat A.cc template <typename> struct Foo { void fun(); // Note: no definition }; int main () { Foo<void>().fun(); } $ cat B.cc #include <iostream> template <typename> struct Foo { void fun(); }; template <typename T> void Foo<T>::fun() { std::cout << "fun!" << std::endl; } // Note: definition with extern linkage template struct Foo<void>; // explicit instantiation upon void $ g++ A.cc B.cc $ ./a.out fun!

Sin embargo, debe crear instancias explícitas para todos los argumentos de plantilla que se utilizarán, de lo contrario

$ cat A.cc template <typename> struct Foo { void fun(); // Note: no definition }; int main () { Foo<float>().fun(); } $ g++ A.cc B.cc undefined reference to `Foo<float>::fun()''

Una pequeña nota sobre la búsqueda en dos fases: si el compilador realmente implementa la búsqueda en dos fases no está dictado por el estándar. Sin embargo, para estar conforme, debería funcionar como si lo hiciera (al igual que la adición o la multiplicación no necesariamente deben realizarse usando instrucciones de la CPU de adición o multiplicación).


Editar : resulta que lo que escribí a continuación es contrario al estándar de C ++. Es cierto para Visual C ++, pero falso para los compiladores que usan "búsqueda de nombre de dos fases".

Hasta donde yo sé, lo que dices es correcto. Las plantillas se instanciarán cuando se usen realmente (incluso cuando se declaren como miembros de otro tipo, pero no cuando se mencionen en una declaración de función (que no tenga un cuerpo)) o como resultado de instancias explícitas.

Un problema con las plantillas es que si utiliza la misma plantilla (por ejemplo, vector) en varias unidades de compilación diferentes (archivos .cpp), el compilador repite el trabajo de instanciar la plantilla en cada archivo .cpp, lo que ralentiza la compilación. IIRC, GCC tiene algún mecanismo (no estándar?) Que se puede usar para evitar esto (pero no uso GCC). Pero Visual C ++ siempre repite este trabajo, a menos que utilice la creación de instancias explícitas en un encabezado precompilado (pero incluso esto ralentizará su compilación, ya que un archivo PCH más grande tarda más en cargarse). Posteriormente, el enlazador elimina los duplicados. Nota : un comentario a continuación está vinculado a una page que nos dice que no todos los compiladores operan de esta manera. Algunos compiladores difieren la instanciación de funciones hasta el tiempo de enlace, que debería ser más eficiente.

Una plantilla no está completamente instanciada cuando se usa por primera vez. En particular, las funciones en la plantilla no se instanciarán hasta que realmente se llamen. Puede verificar esto fácilmente agregando una función sin sentido a una plantilla que está utilizando activamente:

void Test() { fdsh "s.w" = 6; wtf? }

No obtendrá un error a menos que crea una instancia explícita de la plantilla o intente llamar a la función.

Espero que las bibliotecas estáticas (y los archivos de objeto) almacenen el código objeto de todas las plantillas que fueron creadas. Pero si su programa tiene una determinada biblioteca estática como dependencia, no puede llamar a las funciones de plantilla que ya estaban creadas allí, al menos no en VC ++, que siempre requiere el código fuente (con cuerpos de función) de una clase de plantilla en Para llamar a funciones en él.

No creo que sea posible llamar a una función de plantilla en una biblioteca compartida (cuando no tiene el código fuente de la función de plantilla a la que desea llamar).