Templates C++

Anuncio
Templates C++
Escrito por Operata
Los templates son una inclusion posterior al lenguaje C++, que siempre han sido mas
misteriosos para mi que el resto del lenguaje. Este texto de una universidad explica el tema.
La generalidad es una propiedad que permite definir una clase o una función sin tener que
especificar el tipo de todos o alguno de sus miembros. Esta propiedad no es imprescindible en
un lenguaje de programación orientado a objetos y ni siquiera es una de sus características.
Esta característica del C++ apareció mucho más tarde que el resto del lenguaje, al final de la
década de los ochenta. Esta generalidad se alcanza con las plantillas (templates).
La utilidad principal de este tipo de clases o funciones es la de agrupar variables cuyo
tipo no esté predeterminado. Así el funcionamiento de una pila, una cola, una lista, un conjunto,
un diccionario o un array es el mismo independientemente del tipo de datos que almacene (int,
long, double, char, u objetos de una clase definida por el usuario). En definitiva estas clases se
definen independientemente del tipo de variables que vayan a contener y es el usuario de la
clase el que debe indicar ese tipo en el momento de crear un objeto de esa clase.
1 Introducción
Hemos indicado que en la programación clásica existía una clara diferenciación entre
los datos y su manipulación, es decir, entre los datos y el conjunto de algoritmos para
manejarlos. Los datos eran tipos muy simples, y generalmente los algoritmos estaban
agrupados en funciones orientadas de forma muy específica a los datos que debían manejar.
Posteriormente la POO introdujo nuevas facilidades: La posibilidad de extender elconcepto de
dato, permitiendo que existiesen tipos más complejos a los que se podía asociar la
operatoria necesaria.
Esta nueva habilidad fue perfilada con un par de mejoras adicionales: La posibilidad de cultación de determinados detalles internos, irrelevantes para el usuario, y lacapacidad de
herencia simple o múltiple.
1 / 11
Templates C++
Escrito por Operata
Observe que las mejoras introducidas por la POO se pueden sintetizar en tres palabras:
Composición, ocultación y herencia. De otro lado, la posibilidad de incluir juntos los datos y su
operatoria no era exactamente novedosa. Esta circunstancia ya existía de forma subyacente en
todos los lenguajes. Recuerde que el concepto de entero (int en C) ya incluye implícitamente
todo un álgebra y reglas de uso para dicho tipo. Observe también que la POO mantiene un
paradigma de programación orientado al dato (o estructuras de datos). De hecho los "Objetos"
se definen como instancias concretas de las clases, y estas representan nuevos tipos-de-datos,
de modo que POO es sinónimo de Programación Orientada a Tipos-de-datos Desde luego la
POO supuso un formidable avance del arsenal de herramientas de programación. Incluso en
algunos casos, un auténtico balón de oxígeno en el desarrollo y mantenimiento de aplicaciones
muy grandes, en las que se estaba en el límite de lo factible con las técnicas programación
tradicional. Sin embargo, algunos teóricos seguían centraron su atención en los algoritmos.
Algo que estaba ahí también desde el principio. Se dieron cuenta que recuentemente las
manipulaciones contienen un denominador común que se repite bajo apariencias diversas. Por
ejemplo: La idea de ordenación "Sort" se repite infinidad de veces en la programación, aunque
los objetos a ordenar y los criterios de ordenación varíen de un caso a otro.
Alrededor de esta idea surgió un nuevo paradigma denominado programación genérica o
funcional
.
La
programación genérica está mucho más centrada en los algoritmos que en los datos y su
postulado fundamental puede sintetizarse en una palabra: generalización. Significa que, en la
medida de lo posible, los algoritmos deben ser parametrizados al máximo y expresados de la
forma más independiente posible de detalles concretos, permitiendo así que puedan servir para
la mayor variedad posible de tipos y estructuras de datos.
Los expertos consideran que la parametrización de algoritmos supone una aportación a
las técnicas de programación, al menos tan importante, como fue en su momento la
introducción del concepto de herencia, y que permite resolver algunos problemas que aquella
no puede resolver.
Observe que la POO y la programación genérica representan enfoques en cierta forma
ortogonales entre si:
La programación orientada al dato razona del siguiente modo: Representemos un tipo de
dato genérico (por ejemplo int) que permita representar objetos con ciertas características
comunes (peras y manzanas por ejemplo). Definamos también que operaciones pueden
2 / 11
Templates C++
Escrito por Operata
aplicarse a este tipo (por ejemplo aritméticas) y sus reglas de uso, independientemente que el
tipo represente peras o manzanas en cada caso.
Por su parte la programación funcional razona lo siguiente: Construyamos un algoritmo
genérico (por ejemplo sort), que permita representar algoritmos con ciertas características
comunes (ordenación de cadenas alfanuméricas y vectores por ejemplo). Definamos también a
que tipos pueden aplicarse a este algoritmo y sus reglas de uso, independientemente que el
algoritmo represente la ordenación de cadenas alfanuméricas o vectores.
Con el fin de adoptar los paradigmas de programación entonces en vanguardia, Desde sus
inicios C++ había adoptado conceptos de lenguajes anteriores. Uno de ellos, la
programación estructurada , ya había sido recogida en el diseño de su antecesor directo C.
También adoptó los conceptos de la POO entonces emergente . Posteriormente ha incluido
otros conceptos con que dar soporte a los nuevos enfoques de la programación funcional;
básicamente plantillas y contenedores. Las plantillas, que se introdujeron con la versión del
Estándar de Julio de 1998 son un concepto tomado del Ada. Los contenedores no están
definidos en el propio lenguaje, sino en la Librería Estándar.
2 Concepto de plantilla
Las plantillas ("Templates"), también denominadas tipos parametrizados, son un
mecanismo C++ que permite que un tipo pueda ser utilizado como parámetro en la definición
de una clase o una función. Ya se trate de clases o funciones, la posibilidad de utilizar un tipo
como parámetro en la definición, posibilita la existencia de entes de nivel de abstracción
superior al de función o clase concreta.
Podríamos decir que se trata de funciones o clases genéricas; parametrizadas (de ahí su
nombre). Las "instancias" concretas de estas clases y funciones conforman familias de
funciones o clases relacionadas por un cierto "denominador común", de forma que
3 / 11
Templates C++
Escrito por Operata
proporcionan un medio simple de representar gran cantidad de conceptos generales y un
medio sencillo para combinarlos. Para ilustrarlo intentaremos una analogía: si la clase Helado-de-Fresa representara los
helados de fresa, de los que las "instancias" concretas serían distintos tamaños y formatos de
helados de este sabor, una plantilla Helado-de - sería capaz de generar las clases
Heladode-fresa; Helado-de-vainilla; Helado-de-chocolate, etc. con solo cambiar
adecuadamente el argumento
. En realidad
respondería al concepto genérico de "Helado-de". Las instancias concretas de la plantilla
forman una familia de productos relacionados (helados de diversos sabores). Forzando al
máximo la analogía diríamos "especialidades".
Observe que el mecanismo de plantillas C++ es en realidad un generador de código
parametrizado. La conjunción de ambas capacidades: Generar tipos (datos) y código
(algoritmos) les confiere una extraordinaria potencia. Si bien el propio inventor del lenguaje
reconoce que a costa de "cierta complejidad", debida principalmente a la variedad de contextos
en los que las plantillas pueden ser definidas y utilizadas.
La idea central a resaltar aquí es que una plantilla genera la definición de una clase o de una
función mediante uno o varios parámetros. A esta instancia concreta de la clase o función se la
denomina una especialización o especialidad de la plantilla. Un aspecto crucial del sistema es
que los parámetros de la plantilla pueden ser a su vez plantillas. Para manejar estos conceptos
utilizaremos la siguiente terminología:
· Plantilla de clase (template class) o su equivalente: clase genérica.
· Plantilla de función (template function) o su equivalente: función genérica.
Definida una plantilla, al proceso por el cual se obtienen clases especializadas (es decir para
alguno de los tipos de datos específicos) se denomina instanciación o de la plantilla o
especialización de una clase o método genérico. A las funciones y clases generadas para
determinados tipos de datos se las denomina entonces como clases o funciones concretas o
especializadas.
Como se ha indicado, las plantillas representan una de las últimas
implementaciones del lenguaje y constituyen una de las soluciones adoptadas por C++ para
4 / 11
Templates C++
Escrito por Operata
dar soporte a la programación genérica. Aunque inicialmente fueron introducidas para dar
soporte a las técnicas que se necesitaban para la Librería Estándar (para lo que se mostraron
muy adecuadas), son también oportunas para muchas situaciones de programación.
Precisamente la exigencia fundamental de diseño de la citada librería era lograr algoritmos con
el mayor grado de abstracción posible, de forma que pudieran adaptarse al mayor número de
situaciones concretas.
El tiempo parece demostrar que sus autores realizaron un magnífico trabajo que va
más allá de la potencia, capacidad y versatilidad de la Librería Estándar C++ y de que
otroslenguajes hayan seguido la senda marcada por C++ en este sentido. Tal es el caso de
Java, con su JGL ("Java Generic Library"). Lo que comenzó como una herramienta para la
generación parametrizada de nuevos tipos de datos (clases), se ha convertido por propio
derecho en un nuevo paradigma, la metaprogramación (programas que escriben programas).
De lo dicho hasta ahora puede deducirse, que las funciones y clases obtenidas a partir de
versiones genéricas (plantillas), pueden obtenerse también mediante codificación manual (en
realidad no se diferencian en nada de estas últimas). Aunque en lo tocante a eficacia y tamaño
del código, las primeras puedan competir en igualdad de condiciones con las obtenidas
manualmente. Esto se consigue porque el uso de plantillas no implica ningún mecanismo de
tiempo de ejecución. Las plantillas dependen exclusivamente de las propiedades de los tipos
que utiliza como parámetros y todo se resuelve en tiempo de compilación. Podríamos pensar
que su resolución es similar a las macros de C en el que se hacía una sustitución textual de la
expresión indicada en el define por la expresión puesta a continuación, pero en función de
unos parámetros. De igual forma, cuando se especializa una plantilla, se escribe el código con
los tipos sustitudos por lo indicado en la plantilla, y después se procede a compilar tanto el
código escrito manualmente como el escrito automáticamente por este mecanismo.
Las plantillas representan un método muy eficaz de generar código (definiciones de funciones
y clases) a partir de definiciones relativamente pequeñas. Además su utilización permite
técnicas de programación avanzadas, en las que implementaciones muy sofisticadas se
muestran mediante interfaces que ocultan al usuario la complejidad, mostrándola solo en la
medida en que necesite hacer uso de ella. De hecho, cada una de las potentes abstracciones
que se utilizan en la Librería Estándar está representada como una plantilla. A excepción de
algunas pocas funciones, prácticamente el 100% de la Librería Estándar está relacionada con
las plantillas, de ahí que hasta ahora no se halla hecho mucha referencia a esta librería
perteneciente al estándar de C++.
La palabra clave template
C++ utiliza una palabra clave específica template para declarar y definir funciones y
5 / 11
Templates C++
Escrito por Operata
clases genéricas. En estos casos actúa como un especificador de tipo y va unido al par de
ángulos que delimitan los argumentos de la plantilla:
template void fun(T& ref); // función genérica
template class C {/*...*/}; // clase genérica
En algunas otras (raras) ocasiones la palabra template se utiliza como calificador para indicar
que eterminada entidad es una plantilla (y en consecuencia puede aceptar argumentos)
cuando el compilador no puede deducirlo por sí mismo.
Bien, pues una vez expuestas las ideas principales referentes al concepto de plantilla, vamos a
ver en primer lugar como se realzan funciones genéricas, para después explicar el concepto y
el modo de funcionamiento de las clases genéricas.
3 Plantillas de funciones
Para ilustrar gráficamente su utilidad utilizaremos un ejemplo clásico: queremos construir
una función max(a, b) que pueda utilizarse para obtener el mayor de dos valores, suponiendo
que estos sean de cualquier tipo capaz de ser ordenado, es decir, cualquier tipo en el que se
pueda establecer un criterio de ordenación (establecemos a > b si a está después que b en el
orden).
El problema que presenta C++ para esta propuesta es que al ser un
lenguaje fuertemente tipado, la declaración c max(a, b) requiere especificar el tipo de
argumentos y valor devuelto. En realidad se requiere algo así:
tipoT max(tipoT a, tipoT b);
y la sintaxis del lenguaje no permite que tipoT sea algo variable. Una posible solución es
sobrecargar la función max(), definiendo tantas versiones como tipos distintos debamos utilizar.
6 / 11
Templates C++
Escrito por Operata
double max(double a,double b){return a>b?a:b;}
int max(int a,int b){return a>b?a:b;}
…
Otra alternativa sería utilizar una macro:
#define max(a, b) ((a > b) ? a : b)
pero esto presenta sus inconvenientes. Empezando porque su utilización permitiría
comparar un entero con una estructura o una matriz, algo que está claramente fuera del
propósito de la función que pretendemos.
La solución al problema enunciado es utilizar una función genérica (plantilla). La sintaxis de su
definición es la siguiente:
template T max(T a, T b)
{
return (a > b) ? a : b;
}
es la lista de parámetros. Representa el/los parametros de la plantilla. Los parámetros
de una plantilla funciona en cierta forma como los argumentos de una macro (el trabajo de esta
macro es generar código de funciones). Es importante significar que utilizamos dos conceptos
distintos (aunque relacionados): los parámetros de la plantilla (contenidos en la lista template )
y los argumentos de la función (argumentos con que se invoca la función en cada caso
concreto).
7 / 11
Templates C++
Escrito por Operata
Lo mismo que en las funciones explícitas, las genéricas pueden ser declaradas antes de su
utilización:
template T max(T, T);
y definidas después:
template T max(T a, T b)
{
return (a > b) ? a : b;
}
La idea fundamental es que el compilador deduce los tipos concretos de los parámetros
de la plantilla de la inspección de los argumentos actuales utilizados en la invocación . Por
ejemplo, la plantilla anterior puede ser utilizada mediante las siguientes sentencias:
int i, j;
UnaClase a, b;
...
int k = max(i, j); // (1)
UnaClase c = max(a, b); // (2)
8 / 11
Templates C++
Escrito por Operata
En (1) los argumentos de la función son dos objetos tipo int; mientras en (2) son
dos objetos tipo UnaClase. El compilador es capaz de construir dos funciones aplicando los
parámetros adecuados a la plantilla. En el primer caso, el parámetro es un int; en el segundo
un tipo UnaClase. Como veremos más adelante, es de la máxima importancia que el
compilador sea capaz de deducir los parámetros de la plantilla a partir de los argumentos
actuales (los utilizados en cada invocación de la función), así como las medidas sintácticas
adoptadas cuando esto no es posible por producirse ambigüedad.
Una función genérica puede tener más argumentos que la plantilla. Por ejemplo:
template void func(T, inf, char, long, ...);
También puede tener menos:
template void func();
La forma de operar en este caso para que el compilador deduzca el parámetro correcto
T a utilizar en la plantilla, se muestra más adelante cuando se hable de la especificación
explícita de los argumentos de una plantilla.
Llegados a este punto es conveniente hacer algunas observaciones importantes: Las
funciones genéricas son entes de nivel de abstracción superior a las funciones concretas (en
este contexto preferimos llamarlas funciones explícitas), pero
las funciones genérica
s solo tienen existencia en el código fuente
y en la mente del programador. Hemos dicho que el mecanismo de plantillas C++ se resuelve
en tiempo de compilación, de modo que en el ejecutable, y durante la ejecución, no existe nada
parecido a una función genérica
, solo
existen especializaciones
(instancias de la función genérica).
Esta característica de las funciones genéricas es de la mayor importancia. Supone que pueden
escribirse algoritmos muy genéricos en los que
los detalles dependen del tipo de
objeto con el que se utiliza
(el algoritmo). En nuestro ejemplo, el criterio que define que objeto
a
o
b
es mayor, no está contenido en la función max(), sino en la propia clase a que pertenecen
ambos objetas en donde ha debido sobrecargarse el operador > para ese tipo concreto. Esta
es justamente la premisa fundamental de la programación genérica.
La instanciación de la plantilla se produce cuando el compilador encuentra que es necesaria
una versión concreta (especialidad) de la función genérica. Esto sucede cuando existe una
9 / 11
Templates C++
Escrito por Operata
invocación como en el ejemplo, la línea (2) , o se toma la dirección de la función (por ejemplo
para iniciar un puntero-a-función). Entonces se genera el código apropiado en concordancia
con el tipo de los argumentos actuales.
Ocurre que si esta instancia aparece más de una vez en un módulo, o es generada en más
de un módulo, el enlazador las refunde automáticamente en una sola definición, de forma que
solo exista una copia de cada instancia. Dicho en otras palabras: en la aplicación resultante
solo existirá una definición de cada función. Por contra, si no existe ninguna invocación no se
genera ningún código.
Aunque la utilización de funciones genéricas conduce a un código elegante y reducido, que no
se corresponde con el resultado final en el ejecutable. Si la aplicación utiliza muchas plantillas
con muchos tipos diferentes, el resultado es la generación de gran cantidad de código con
el consiguiente consumo de espacio. Esta crecimiento del código es conocida como "Code
bloat", y puede llegar a ser un problema. En especial cuando se utilizan las plantillas de la
Librería Estándar, aunque existen ciertas técnicas para evitarlo. Como regla general, las
aplicaciones que hace uso extensivo de plantillas resultan grandes consumidoras de memoria
(es el costo de la comodidad).
Puesto que cada instancia de una función genérica es una verdadera función, cada
especialización dispone de su propia copia de las variables estáticas locales que hubiese. Se
les pueden declarar punteros y en general gozan de todas las propiedades de las funciones
normales, incluyendo la capacidad de sobrecarga Veamos un caso concreto con una función
genérica que utiliza tanto una clase Vector como un entero:
#include
class Vector
{
public:
float x, y;
bool operator>(Vector v)
10 / 11
Templates C++
Escrito por Operata
{
return ((x*x + y*y) > (v.x*v.x + v.y*v.y))? true: false;
}
};
template T max(T a, T b){ return (a > b) ? a : b;}
void main()
{
Vector v1 = {2, 3}, v2 = {1, 5};
Int x = 2, y = 3;
cout
11 / 11
Descargar