Templates ( eng. template ) es una herramienta de lenguaje C ++ diseñada para codificar algoritmos generalizados , sin estar vinculado a algunos parámetros (por ejemplo, tipos de datos , tamaños de búfer, valores predeterminados).
En C++ es posible crear plantillas de funciones y clases .
Las plantillas le permiten crear clases y funciones parametrizadas. El parámetro puede ser cualquier tipo o un valor de uno de los tipos permitidos (entero, enumeración, puntero a cualquier objeto con un nombre accesible globalmente, referencia). Por ejemplo, necesitamos alguna clase:
clase AlgunaClase { int AlgúnValor ; int SomeArray [ 20 ]; ... };Para un propósito específico, podemos usar esta clase. Pero, de repente, el objetivo ha cambiado un poco y se necesita otra clase. Ahora necesitamos 30 elementos de matriz y un tipo de elemento SomeArrayreal . Entonces podemos abstraernos de los tipos concretos y usar plantillas con parámetros. Sintaxis: al principio, antes de declarar la clase, declaramos la plantilla, es decir , especificamos los parámetros entre paréntesis angulares. En nuestro ejemplo: SomeValueSomeArraytemplate
template < int ArrayLength , typename SomeValueType > class SomeClass { AlgúnTipoValor AlgúnValor ; SomeValueType SomeArray [ ArrayLength ]; ... };Entonces para el primer caso (con entero SomeValue y SomeArray de 20 elementos) escribimos:
SomeClass < 20 , int > SomeVariable ;para el segundo:
SomeClass < 30 , double > SomeVariable2 ;Aunque las plantillas proporcionan una forma abreviada de una pieza de código, su uso en realidad no acorta el código ejecutable, ya que el compilador crea una instancia separada de una función o clase para cada conjunto de opciones. Como resultado, desaparece la capacidad de compartir código compilado dentro de bibliotecas compartidas.
Una plantilla de función comienza con la palabra clave templateseguida de una lista de parámetros entre paréntesis angulares. Luego viene la declaración de la función:
template < typename T > void sort ( T array [], int size ); // prototipo: la plantilla de clasificación se declara pero no se define plantilla < typenameT > _ void sort ( arreglo T [], tamaño int ) // declaración y definición { Tt ; _ para ( int i = 0 ; i < tamaño - 1 ; i ++ ) para ( int j = tamaño - 1 ; j > i ; j -- ) si ( matriz [ j ] < matriz [ j -1 ]) { t = matriz [ j ]; matriz [ j ] = matriz [ j -1 ]; matriz [ j -1 ] = t ; } } template < int BufferSize > // parámetro entero char * lectura () { char * Buffer = new char [ BufferSize ]; /* leer datos */ búfer de retorno ; }La palabra clave es typenamerelativamente reciente, por lo que el estándar [1] permite el uso classen lugar de typename:
plantilla < claseT > _En lugar de T , se acepta cualquier otro identificador.
El ejemplo más simple es la determinación del mínimo de dos cantidades.
Si a es menor que b, devuelve a, de lo contrario, devuelve b
En ausencia de plantillas, el programador tiene que escribir funciones separadas para cada tipo de datos utilizado. Aunque muchos lenguajes de programación definen una función mínima incorporada para tipos elementales (como números enteros y números reales), tal función puede ser necesaria para complejos (por ejemplo, "tiempo" o "cadena") y muy complejos (" jugador” en un juego en línea ) objetos.
Así es como se ve la plantilla de función mínima:
plantilla < typenameT > _ T min ( Ta , T b ) _ { devolver a < b ? un : b ; }Para llamar a esta función, simplemente puede usar su nombre:
min ( 1 , 2 ); min ( 'a' , 'b' ); min ( cadena ( "abc" ), cadena ( "cde" ) );En términos generales, para llamar a una función de plantilla, debe proporcionar valores para todos los parámetros de la plantilla. Para ello, tras el nombre de la plantilla, se indica una lista de valores entre paréntesis angulares:
int yo [] = { 5 , 4 , 3 , 2 , 1 }; ordenar < int > ( i , 5 ); char c [] = "bvgda" ; ordenar < char > ( c , strlen ( c ) ); ordenar < int > ( c , 5 ); // error: sort<int> tiene un parámetro int[], no char[] char * ReadString = leer < 20 > (); eliminar [] Cadena de lectura ; ReadString = leer < 30 > ();Para cada conjunto de opciones, el compilador genera una nueva instancia de la función. El proceso de creación de una nueva instancia se denomina creación de instancias de plantilla .
En el ejemplo anterior, el compilador creó dos especializaciones de plantillas de funciones sort(para los tipos chary int) y dos especializaciones de plantillas read(para los valores BufferSize20 y 30). Lo último probablemente sea un desperdicio, ya que para cada valor posible del parámetro, el compilador creará más y más instancias nuevas de funciones que diferirán en una sola constante.
Derivación de los valores de los parámetrosEn algunos casos, el compilador puede inferir (determinar lógicamente) el valor de un parámetro de plantilla de función a partir de un argumento de función. Por ejemplo, al llamar a la función descrita anteriormente, no es sortnecesario especificar el parámetro de la plantilla (si coincide con el tipo de los elementos del argumento de la matriz):
int yo [ 5 ] = { 5 , 4 , 3 , 2 , 1 }; ordenar ( i , 5 ); // llamar a sort<int> char c [] = "bvgda" ; ordenar ( c , strlen ( c ) ); // llamar a sort<char>La eliminación también es posible en casos más complejos .
En el caso de usar plantillas de clase con parámetros enteros, también es posible inferir esos parámetros. Por ejemplo:
plantilla < tamaño int > clase IntegerArray { matriz int [ tamaño ]; /* ... */ }; template < int size > // Prototipo de plantilla void PrintArray ( IntegerArray < size > array ) { /* ... */ } // Llamada de plantilla // Usando el objeto de plantilla IntegerArray < 20 > ia ; Matriz de impresión ( ia );Las reglas de inferencia se introducen en el lenguaje para facilitar el uso de una plantilla y evitar posibles errores, como intentar sort< int >ordenar una matriz de caracteres.
Si se puede inferir un parámetro de plantilla a partir de varios argumentos, el resultado de la inferencia debe ser exactamente el mismo para todos esos argumentos. Por ejemplo, las siguientes llamadas son erróneas:
min ( 0 , 'a' ); min ( 7 , 7.0 );Los errores asociados con el uso de parámetros de plantilla específicos no se pueden detectar antes de usar la plantilla. Por ejemplo, la plantilla minen sí no contiene errores, pero usarla con tipos para los que la operación '<'no está definida dará como resultado un error:
estructura A { en un ; }; A obj1 , obj2 ; min ( obj1 , obj2 );Si ingresa la operación '<'antes del primer uso de la plantilla, se eliminará el error. Así es como se manifiesta la flexibilidad de las plantillas en C++ :
amigo operador bool en línea < ( const A & a1 , const A & a2 ) { return a1 . a < a2 . un ; } min ( obj1 , obj2 );En una clase que implementa una lista enlazada de enteros, los algoritmos para agregar un nuevo elemento a la lista y buscar el elemento deseado no dependen del hecho de que los elementos de la lista sean enteros. Los mismos algoritmos se aplicarían a una lista de caracteres, cadenas, fechas, clases de jugadores, etc.
plantilla < claseT > _ lista de clases { /* ... */ público : void Add ( const T & Element ); bool Buscar ( const T & Elemento ); /* ... */ };Para usar una plantilla de clase, debe especificar sus parámetros:
Lista < int > li ; Lista < cadena > ls ; li . suma ( 17 ); ls . Agregar ( "¡Hola!" );Los parámetros de plantilla pueden ser: parámetros de tipo, parámetros de tipo regular, parámetros de plantilla.
Puede especificar valores predeterminados para parámetros de cualquier tipo.
plantilla < clase T1 , // parámetro de tipo nombre de tipo T2 , // parámetro de tipo int I , // parámetro de tipo regular T1 DefaultValue , // parámetro de tipo regular plantilla < clase > clase T3 , // clase de parámetro de plantilla Carácter = char // predeterminado parámetro > Parámetros de la plantillaSi es necesario usar la misma plantilla en una plantilla de clase o función, pero con diferentes parámetros, entonces se usan los parámetros de plantilla. Por ejemplo:
plantilla < tipo de clase , plantilla < clase > contenedor de clase > referencias cruzadas de clase { Contenedor < Tipo > mems ; Contenedor < Tipo * > referencias ; /* ... */ }; CrossReferences < Fecha , vector > cr1 ; Referencias cruzadas < cadena , conjunto > cr2 ;Las plantillas de funciones no se pueden utilizar como parámetros de plantilla.
Para los parámetros que son tipos (por ejemplo, el parámetro T de la función de clasificación), la inferencia es posible si el argumento de la función es de uno de los siguientes tipos:
tipo de argumento | Descripción |
---|---|
T const T volatile T |
El tipo en sí T, posiblemente con modificadores consto volatile. plantilla < claseT > _ T ReturnMe ( const T arg ) { return arg ; } Devuélveme ( 7 ); Devuélveme ( 'a' ); |
T* T& T[A] A es una constante |
Un puntero, referencia o matriz de elementos de tipo T.
Un ejemplo es la plantilla de función de ordenación discutida anteriormente. |
Templ<T> Templ - nombre de plantilla de clase |
Como argumento, la función requiere una especialización específica de alguna plantilla. #incluir <vector> plantilla < claseT > _ clasificación vacía ( vector < T > matriz ) { /* ordenar */ } vector < int > i ; vector < carácter > c ; ordenar ( i ); ordenar ( c ); |
T (*) (args) args - algunos argumentos |
Puntero a una función que devuelve tipo T. plantilla < claseT > _ T * CreateArray ( T ( * GetValue )(), tamaño int constante ) { T * Matriz = nueva T [ tamaño ]; para ( int i = 0 ; i < tamaño ; i ++ ) Matriz [ i ] = ObtenerValor (); matriz de retorno ; } int GetZero () { retornar 0 ; } char InputChar () { carcter c ; cn >> c ; devolver c ; } int * ArrayOfZeros = CreateArray ( GetZero , 20 ); char * String = CreateArray ( InputChar , 40 ); |
type T::* T Class::* tipo - algún tipo Clase - alguna clase |
Un puntero a un miembro de la clase T de un tipo arbitrario. Puntero a un miembro de tipo T de una clase arbitraria. clase Mi Clase { público : en un ; }; plantilla < claseT > _ T & IncrementIntegerElement ( int T ::* Elemento , T & Objeto ) { objeto _ * Elemento += 1 ; devolver Objeto ; } plantilla < claseT > _ T IncrementMyClassElement ( T MyClass ::* Element , MyClass & Object ) { objeto _ * Elemento += 1 ; devolver Objeto . * Elemento ; } MiClase Obj ; intn ; _ n = ( IncrementarElementoEntero ( & MiClase :: a , Obj ) ). un ; n = IncrementarMiElementoDeClase ( & MiClase :: a , Obj ); |
type (T::*) (args) T (Class::*) (args) tipo - algún tipo Clase - algunos argumentos de clase - algunos argumentos |
Puntero a una función miembro de clase T de tipo arbitrario. Puntero a una función miembro de tipo T de una clase arbitraria. clase Mi Clase { público : en un ; int IncrementoA (); }; int MiClase::IncrementoA () { return ++ a ; } plantilla < claseT > _ T & CallIntFunction ( int ( T ::* Función )(), T & Objeto ) { ( Objeto . * Función )(); devolver Objeto ; } plantilla < claseT > _ T CallMyClassFunction ( T ( MyClass ::* Function )(), MyClass & Object ) { return ( Objeto . * Función )(); } MiClase Obj ; intn ; _ n = ( CallIntFunction ( & MyClass :: IncrementA , Obj ) ). un ; n = CallMyClassFunction ( & MyClass :: IncrementA , Obj ); |
Los miembros de una plantilla de clase son plantillas y tienen la misma parametrización que la plantilla de clase. En particular, esto significa que la definición de las funciones miembro debe comenzar con el encabezado de la plantilla:
plantilla < claseT > _ clase A { vacío f ( datos T ); vacío g ( vacío ); público : un (); }; plantilla < claseT > _ vacío A < T >:: f ( T datos ); plantilla < claseT > _ vacío A < T >:: g ( vacío );Dentro del alcance de la plantilla, no es necesario repetir el especificador. Esto quiere decir que, por ejemplo A<T>::A() , es un constructor , aunque también se puede escribir A<T>::A<T>().
Tipos como miembros de clasesSi el parámetro de plantilla es una clase que tiene un miembro que es de tipo de datos , entonces se debe usar la palabra clave para usar ese miembro typename. Por ejemplo:
contenedor de clase { público : matriz int [ 15 ]; typedef int * iterador ; /* ... */ iterador comenzar () { retornar matriz ; } }; plantilla < clase C > vacío f ( C y vector ) { C :: iterador i = vector . comenzar (); // nombre de tipo de error C :: iterador i = vector . comenzar (); } Plantillas como miembros de clasesTambién hay problemas con los miembros de la plantilla. Si una plantilla (ConvertTo()), que es miembro de una clase (A), que a su vez es un parámetro de plantilla (f), se usa en esta plantilla (f) y no permite la inferencia de parámetros, entonces el calificador debe ser utilizado template:
clase A { /* ... */ público : plantilla < clase T > T & ConvertTo (); plantilla < clase T > void ConvertFrom ( const T & data ); }; plantilla < claseT > _ vacío f ( T Contenedor ) { int i1 = Contenedor . plantilla ConvertTo < int > () + 1 ; contenedor _ ConvertirDesde ( i1 ); // no se necesita calificador }La metaprogramación de plantillas en C++ adolece de muchas limitaciones, incluidos problemas de portabilidad, falta de depuración o soporte de E/S durante la creación de instancias de plantillas, largos tiempos de compilación, mala legibilidad del código, diagnósticos de errores deficientes y mensajes de error oscuros [2] . El subsistema de plantillas de C++ se define como un lenguaje de programación puramente funcional completo de Turing, pero los programadores de estilo funcional ven esto como una provocación y son reacios a reconocer a C++ como un lenguaje exitoso [3] .
Muchos lenguajes ( Java 5, Ada , Delphi 2009) implementan soporte de programación genérica de una manera más simple, algunos incluso a nivel de sistema de tipos (ver Eiffel , y polimorfismo paramétrico en la familia de lenguajes ML ); tales lenguajes no necesitan mecanismos similares a las plantillas de C++.
Las instalaciones de sustitución de macros de C, aunque no están completas en Turing, son suficientes para la programación de bajo nivel en la programación generativa , y sus capacidades se han ampliado significativamente en C99 .
El lenguaje D tiene plantillas que son más poderosas que C++. [4] .
Tipos de datos | |
---|---|
Ininterpretable | |
Numérico | |
Texto | |
Referencia | |
Compuesto | |
resumen |
|
Otro | |
Temas relacionados |