Plantillas C++

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 30 de marzo de 2016; las comprobaciones requieren 29 ediciones .

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.

Plantillas de funciones

Plantilla Descripción Sintaxis

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.

Ejemplo de uso

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" ) );

Llamada de función de plantilla

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ámetros

En 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 );

Errores en las plantillas

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 );

Plantillas de clase

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 ); /* ... */ };

Uso de plantillas

Para usar una plantilla de clase, debe especificar sus parámetros:

Lista < int > li ; Lista < cadena > ls ; li . suma ( 17 ); ls . Agregar ( "¡Hola!" );

Detalles técnicos

Opciones de plantilla

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 plantilla

Si 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.

Reglas para inferir argumentos de plantillas de funciones

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 );

Miembros de clases de plantilla

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 clases

Si 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 clases

Tambié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 }

Crítica y comparación con alternativas

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] .

Véase también

Notas

  1. Estándar C++ "Estándar para el lenguaje de programación C++": ISO/IEC 14882 1998 .
  2. K. Czarnecki, J. O'Donnell, J. Striegnitz, W. Taha. Implementación de DSL en metaocaml, plantilla haskell y C++ . — Universidad de Waterloo, Universidad de Glasgow, Centro de Investigación Julich, Universidad Rice, 2004. .
    Cita: La metaprogramación de plantillas de C++ adolece de una serie de limitaciones, incluidos problemas de portabilidad debido a las limitaciones del compilador (aunque esto ha mejorado significativamente en los últimos años), falta de soporte de depuración o IO durante la creación de instancias de plantillas, largos tiempos de compilación, errores largos e incomprensibles , mala legibilidad del código y pobre informe de errores.
  3. Sheard T., Jones Plantilla SP Metaprogramación para Haskell  // Haskell Workshop. -Pittsburgh: ACM 1-58113-415-0/01/0009, 2002. .
    Una cita del provocador artículo de Robinson identifica las plantillas de C++ como un éxito importante, aunque accidental, del diseño del lenguaje C++. A pesar de la naturaleza extremadamente barroca de la metaprogramación de plantillas, las plantillas se utilizan de formas fascinantes que se extienden más allá de los sueños más salvajes de los diseñadores de lenguajes. Quizás sorprendentemente, en vista del hecho de que las plantillas son programas funcionales, los programadores funcionales han tardado en capitalizar el éxito de C++.
  4. ↑ Marte digital : lenguaje de programación D 2.0  

Literatura

  • David Vandevoerd, Nicholas M. Josattis. Plantillas de C ++: la guía completa = Plantillas de C++: la guía completa. - M .: "Williams" , 2003. - S.  544 . — ISBN 0-201-73484-2 .
  • Podbelsky V. V. 6.9. Plantillas de funciones //Capítulo 6. Funciones, punteros, referencias // Lenguaje C++ / revisión. Dadaev Yu. G. - 4. - M. : Finanzas y estadísticas , 2003. - S. 230-236. — 560 págs. - ISBN 5-279-02204-7 , UDC 004.438Si (075.8) LBC 32.973.26-018 1ya173.

Enlaces