martes, 1 de septiembre de 2009

Covariancia y contravariancia (IV)

El problema de los punteros

Supongamos que tenemos tres tipos de punteros. Los punteros de sólo lectura, los de sólo escritura y los de escritura-lectura. Si apuntan a un tipo T, los llamaremos respectivamente PL(T), PE(T) y P(T).

Debe estar claro que si un puntero es de escritura-lectura entonces es de escritura y de lectura.

PL(T) <: P(T)

PE(T) <: P(T)

Ahora bien, lo que no está tan claro es si tengo un tipo y un subtipo (como los muebles M y las sillas S) si ocurrirá que

PL(S) <: PL(M) ???

PE(S) <: PE(M) ???

P(S) <: P(M) ???

PL(S) <: P(M) ???

Y todas las dieciocho combinaciones.

Para dilucidar si los punteros son covariantes o contravariantes vamos a inventarnos unas operaciones de lectura y de escritura sobre ellos. Estas operaciones serán:

  • altura(): Devuelve la altura de un mueble.
  • patas(): Devuelve el número de patas de un mueble.
  • crea_silla(int, int): Crea una silla con la altura y el número de patas dados
  • crea_mueble(int): Crea un mueble con la altura dada.

Ahora, usaremos los punteros plm de tipo PL(M), pls de tipo PL(S), pem de tipo PE(M) y pes de tipo PE(S) y veremos cuándo se pueden o no sustituir unos por otros.

pls.patas() //OK, mismo tipo

plm.patas() //Mal, los muebles no tienen patas

plm.altura() // OK, mismo tipo

pls.altura() //Bien, las sillas tienen altura

pes.crea_silla(110, 4) // OK, mismo tipo

pem.crea_silla(110, 4) // Bien, creamos un mueble que es una silla

pem.crea_mueble(150) // OK, mismo tipo

pes.crea_mueble(150) // Mal, no sabemos las patas que tiene la silla

En definitiva, hemos podido usar PL(S) donde había un PL(M) y hemos podido usar un PE(M) donde había un PE(S) de forma que:

S <: M

PL(S) <: PL(M)

PE(S) :> PE(M)

Los punteros de lectura son covariantes y los de escritura son contravariantes. Obviamente, como los punteros de escritura-lectura tienen que compartir ambas propiedades, llegamos a la conclusión que o bien S=M o bien no hay forma de relacionar P(S) con P(M). Esto se puede ver en el libro de Pierce, en el apartado 15.5.

La conversión de const char** a char** 

Este curioso comportamiento tiene sus repercusiones en lenguajes como el C++ y particularmente en las conversionse de cualificación CV. En estas conversiones se prohibe la conversión de "un puntero a un puntero a un dato constante" a "un puntero a un puntero a un dato modificable". ¿Por qué? Veamos los tipos:

const char**  char**

P(PL(T))      P(P(T))

Como el constructor de tipos más exterior es el puntero de escritura-lectura, no podemos decir nada de su relación con el otro puntero.

Esto suele extrañar a la gente porque si sólo fueran punteros de un nivel tendríamos

const char*    char*

PL(T)       <: P(T)

Y la conversión sería posible. Cualquier programador de C++ sabe que podemos pasar un puntero normal donde se requiere un puntero constante.

En la siguiente parte veremos la relación del slicing con los punteros de escritura, cómo usar el conocimiento sobre la variancia en los constructores de tipo y la forma que tienen algunos lenguajes de trabajar con la variancia de tipos.

0 comentarios:

Publicar un comentario