2010-05-09 20 views
13

Un colega mío me contó acerca de una pequeña pieza de diseño que ha utilizado con su equipo que hizo que mi mente se pusiera a hervir. Es un tipo de rasgos de la clase que pueden especializarse en una forma muy desacoplada.truco de métodos de plantilla indefinido?

Me ha costado entender cómo podría funcionar, y todavía no estoy seguro de la idea que tengo, así que pensé en pedir ayuda aquí.

Estamos hablando de g ++ aquí, específicamente las versiones 3.4.2 y 4.3.2 (parece funcionar con ambas).

La idea es bastante simple:

1- Definir la interfaz

// interface.h 
template <class T> 
struct Interface 
{ 
    void foo(); // the method is not implemented, it could not work if it was 
}; 

// 
// I do not think it is necessary 
// but they prefer free-standing methods with templates 
// because of the automatic argument deduction 
// 
template <class T> 
void foo(Interface<T>& interface) { interface.foo(); } 

2- Definir una clase, y en el archivo de origen se especializan la interfaz de esta clase (que define sus métodos)

// special.h 

class Special {}; 


// special.cpp 

#include "interface.h" 
#include "special.h" 

// 
// Note that this specialization is not visible outside of this translation unit 
// 
template <> 
struct Interface<Special> 
{ 
    void foo() { std::cout << "Special" << std::endl; } 
}; 

3- Para utilizar, es demasiado simple:

// main.cpp 

#include "interface.h" 

class Special; // yes, it only costs a forward declaration 
       // which helps much in term of dependencies 

int main(int argc, char* argv[]) 
{ 
    Interface<Special> special; 
    foo(special); 
    return 0; 
}; 

Es un símbolo indefinido si ninguna unidad de traducción definió una especialización de Interface para Special.

Ahora, habría pensado que esto requeriría la palabra clave export, que hasta donde sé nunca se ha implementado en g ++ (y solo se implementó una vez en un compilador de C++, con sus autores avisando a alguien que no, dado el tiempo y esfuerzo que les llevó).

sospecho que tiene algo que ver con el enlazador resolver los métodos de plantillas ...

  • ¿Se han conocido nunca nada como esto antes?
  • ¿Cumple con la norma o cree que es una afortunada coincidencia que funcione?

Debo admitir que estoy bastante desconcertado por la construcción ...

Respuesta

10

Como se sospecha @Steward, no es válido.Formalmente es efectivamente que causa un comportamiento indefinido, porque el estándar dictamina que para una infracción no se requiere diagnóstico, lo que significa que la implementación puede hacer lo que quiera de forma silenciosa. En 14.7.3/6

Si una plantilla, una plantilla de miembro o miembro de una plantilla de clase se especializa explícitamente a continuación, que la especialización se declararán antes del primer uso de la especialización que causaría una instancia implícita se lleve a cabo, en cada unidad de traducción en la que se produce dicho uso; no se requiere diagnóstico.

En la práctica, al menos en GCC, que está implícitamente crear instancias de la plantilla primaria Interface<T> ya que la especialización no fue declarada y no es visible en main, y luego llamar a Interface<T>::foo. Si su definición es visible, instala la definición primaria de la función miembro (que es por lo que cuando se define, no funcionaría).

Los símbolos de nombre de función instanciada tienen enlaces débiles porque posiblemente podrían estar presentes varias veces en diferentes archivos de objeto y deben fusionarse en un símbolo en el programa final. Por el contrario, los miembros de especializaciones explícitas que ya no son plantillas tienen una fuerte vinculación por lo que dominarán los símbolos de vinculación débil y harán que la llamada termine en la especialización. Todo esto es detalles de implementación, y el estándar no tiene tal noción de vinculación débil/fuerte. Usted tiene que declarar la especialización antes de crear el special objeto:

template <> 
struct Interface<Special>; 

La Norma establece que desnudo (destacan por mí)

La colocación de las declaraciones especialización explícita de plantillas de funciones, plantillas de clase, miembros funciones de plantillas de clase, miembros de datos estáticos de plantillas de clase, clases de miembros de plantillas de clase, plantillas de clases de miembros de plantillas de clase, plantillas de funciones de miembro de plantillas de clase, funciones de miembro de plantillas de miembros de plantillas de clase, funciones de miembro de plantillas de miembros de plantillas clases, plantillas de funciones miembro de clases miembro de c Las plantillas de clase, etc. y la colocación de declaraciones de especialización parciales de plantillas de clase, plantillas de clases miembro de clases que no son de plantilla, plantillas de clase de miembro de plantillas de clase, etc., pueden afectar si un programa está bien formado de acuerdo con el posicionamiento relativo de las declaraciones de especialización explícita y sus puntos de instanciación en la unidad de traducción como se especifica arriba y abajo. Al escribir una especialización, tenga cuidado con su ubicación; o para hacer que compile será una prueba que encienda su autoinmolación.

+0

Por lo tanto, si lo entiendo correctamente, sería suficiente en "special.h" para incluir "interface.h" y para reenviar declarar la plantilla de especialización 'template <> struct Interface ;' y luego en "main.cpp" para incluir "special.h", para que el programa esté bien formado, aunque la definición del método 'Interface :: foo' nunca aparecerá? –

+0

@ Matthieu, ¡exactamente!Así es como puedes resolver la miseria –

+0

Si leo esto correctamente, esto significa que cualquier programa (múltiples unidades de traducción) donde exista una especialización explícita de una plantilla para un conjunto dado de parámetros, pero en alguna otra unidad de traducción se usa la especialización implícita para el mismo conjunto de parámetros es una violación de ODR que el compilador no es necesario, y probablemente no pueda, para emitir un diagnóstico? Eso es bastante aterrador y otra buena razón para poner siempre especializaciones parciales directamente con la plantilla primaria donde sea posible. – Stewart

5

Eso es bastante limpio. No estoy seguro si está garantizado para trabajar en todas partes. Parece que lo que están haciendo es tener un método de plantilla deliberadamente indefinido y luego definir una especialización escondida en su propia unidad de traducción. Dependen del compilador que usa el mismo nombre para el método de la plantilla de clase original y la especialización, que es el bit que creo que probablemente no sea estándar. El vinculador buscará el método de la plantilla de clase, pero en su lugar encontrará la especialización.

Sin embargo, hay algunos riesgos con esto. Nadie, ni siquiera el enlazador, recogerá múltiples implementaciones del método, por ejemplo. Los métodos de la plantilla se marcarán selectivamente porque la plantilla implica en línea así que si el vinculador ve instancias múltiples, en lugar de emitir un error elegirá la que sea más conveniente.

Sin embargo, es un buen truco, aunque desafortunadamente parece ser una coincidencia afortunada de que funcione.

+0

Bueno, surge el problema de vinculación para cualquier definición de método de plantilla, aunque el riesgo es mayor con la especialización que admito. Aquí se basan en la convención (la especialización está dentro del archivo fuente correspondiente) para evitar la violación de la Regla de una sola definición. Se podría señalar que si el método de plantilla se definiera para el caso genérico, entonces se violaría la ODR porque se instanciaría la plantilla genérica. Buen punto en el tema del cambio de nombre, no se me había ocurrido que podría haber un problema allí también. –

Cuestiones relacionadas