July 2007 - Artículos

Bueno, llevo ya dos tardes con sus noches jugando con el aparatito, y la verdad es que la satisfacción sobre el mismo no es muy alta si se toma en conjunto.

Ya hay gente que ha hablado del aparato, así que voy a asumir que primero vas a leer lo que nuestro amigo de tinta-e nos escribe aquí y aquí, así me voy a ahorrar de dar prolijas explicaciones.

Una vez vistas sus opiniones, que comparto en casi toda su extensión, voy a dar las mías.

El software es más bien mierdosillo, es decir, tiene muy pocas opciones, aunque las que trae son fácilmente configurables y se entiende su funcionalidad sin problemas. Hay que entender que es un programa para leer libros, no para navegar por internet o para escuchar música.

Eso me lleva al tema de por qué han implementado conexión WiFi y Ethernet si ésta sólo sirve para actualizar el equipo. Imagino que en futuras versiones tengan previsto sacar un navegador y todo eso, porque si no, no entiendo el por qué lo han hecho. Para actualizar el aparato sólo hubiera sido necesaria la conexión USB y un software en el PC, y 200 euracos menos de precio.

El libro está basado en la lectura de PDF y HTML. El PDF lo he probado y la verdad es que tampoco se han quemado las cejas en el tema, ya que no existe el modo reflujo ni nada parecido. Si el fichero original está en A4, eso es lo que verás en la pantalla, pudiendo hacer zoom para agrandar el texto. Comentar que un A4 se ve bastante bien y se puede leer sin problemas, así que podremos leer nuestros libros técnicos.

Por otro lado, si queremos tener una experiencia de lectura óptima, tenemos que seguir las normas marcadas en la documentación y hacernos nuestros propios PDF…

También trae una versión hecha en Java del Mobipocket Reader, que la versión 2.10.2 del firmware actualiza para poder hacer búsquedas en diccionarios encriptados (y supongo que también será capaz de leer libros encriptados).

Pero si el paso de hojas en un PDF es aceptable (no tarda más de lo que tú mismo tardarías en pasar la hoja de un libro), en el Mobipocket no lo es ni de lejos; es de una lentitud desesperante, tanto que debo pulsar la orden de avance de página dos líneas antes del final en los textos en castellano y una en los en inglés si quiero que el cambio de hoja no me haga perder el hilo de lo leído. Esto sí que es un problema inaceptable para el aparato, y espero que lo solucionen. Además, mientras que podemos apaisar los PDF, el Mobipocket apenas trae opciones por no decir que no trae ninguna… Solamente podemos buscar en el diccionario (que resulta una operación bastante lenta) y cambiar el tamaño de la fuente (y sería de desear un ajuste de márgenes, posibilidad de leer en columnas y de apaisar el texto).

En su afán por conseguir un formato libro, el aparato no es muy ergonómico que digamos. El paso de hoja se debe hacer siempre con la mano izquierda sobre una barra lateral vertical, con lo que te obliga prácticamente a usar dicha mano para sujetar el aparato. Pero si lo sujetas con esa mano para poder pasar hoja fácilmente, entonces tienes que apretar mucho y terminas cansándote, por lo que deberían haber implementado algún tipo de asa para poder meter los dedos y que el iLiad se apoye en ellos.

En la parte inferior trae cuatro botones de acceso rápido para "Noticias" (es un formato xml/html que el propio fabricante ha especificado y que de momento absolutamente nadie usa y no creo que se use nunca jamás), libros (con los PDF en acceso directo y los de Mobipocket en una carpeta), Documentos (que son varios manuales que trae) y el de notas, que nos sirve para pintar y hacer nuestras cosas a partir de varias plantillas.

Desde mi punto de vista, la barra de avance de página debería haber estado en lugar de los botones, y éstos en el lateral. También habría puesto botones nuevos configurables para lanzar, por ejemplo, un libro favorito, o el último libro abierto, etc.

Poniendo la barra de avance en la parte inferior hubiera posibilitado sujetar el aparato con cualquier mano desde su parte inferior (ya que el centro de gravedad está casi abajo del todo –ya sabemos dónde anda la batería) y hacer avanzar y retroceder las hojas también con cualquier mano. Desde mi punto de vista, esto supone un suspenso absoluto en cuanto a diseño y ergonomía.

Otro tema del hardware que no me gusta nada es la forma de recargar y de conectar al PC, mediante un anexo que se inserta en la parte inferior con un conector de muchos pines muy parecido al de los teléfonos móviles. Este anexo por un lado lleva la conexión al cargador y por el otro los conectores de red y USB. Es una gran idea, aligerando el aparato de varios componentes… pero no se puede leer mientras se está cargando porque corres el riesgo de cargarte el delicado conector…

En la parte de arriba tenemos los zócalos para la tarjeta MMC (que no SD, y es una cosa que no entiendo), CF (me reconoce sin problemas una de 4 GB) y pincho USB (que todavía no he probado)… Extendiendo el tema de la modularidad, también podrían haber hecho insertable un cajoncillo con esos elementos, y seguro que eliminamos unos cuantos gramos del aparato si no vamos a insertar nada.

De la duración de la batería todavía no puedo hablar, porque estoy en la segunda recarga. La primera me duró unas siete horas, pero hay que tener en cuenta que estuve trasteando largo y tendido con el Ethernet, WiFi y demás. Y esta segunda no la puedo medir porque he ido usando el aparato a ratos.

El aparato, una vez conectado por USB al PC, se queda autista, mientras que en el PC aparece como una unidad de disco extraíble más, con toda la estructura de ficheros (no la del sistema) a la vista.

El programa del PC (Companion Sofware, lo llaman), es una castaña pilonga con apenas funcionalidad y que encima de todo no funciona en Vista, ni siquiera en modo administrador. Y para más inri, el Mobipocket de escritorio tampoco detecta el iLiad y no es posible conjuntarlo con él.

Otra cosa que no he podido hacer es que el aparato se conecte al PC mediante Ethernet, aunque la actualización de firmware funciona tanto por WiFi como por Ethernet.

Estas tres últimas cuestiones las he enviado al servicio técnico a ver qué me dicen, pero ya adelanto que se deben a una programación muy chapucera del Companion Software

Entre lo bueno –no todo ha de ser malo-, está la calidad del texto. Mejor que la de un libro impreso, con diferencia absoluta. Y se puede leer a plenísmo sol, de hecho, cuanta más luz incida, mejor se lee (apenas existe un mínimo reflejo quizás originado por la tableta digitalizadora). El ángulo de visión es exactamente el de un libro, por lo que puedes poner el aparato horizontalmente sobre la cama, tumbarte al lado con la cabeza apoyada en la almohada y leer perfectamente.

Otro elemento que me gusta mucho es que la estructura lógica está completamente abierta. Tu dejas un PDF en la carpeta de libros, y cuando vuelves a mirarlo, el aparato ha metido el documento en una carpeta con el mismo nombre, ha asociado un archivo XML que contiene la descripción del libro y más datos, así como otro más para las anotaciones…

Así mismo, y pese a estar desarrollado en Linux (o quizás por eso), el usuario puede programar el aparato sin problemas, de hecho en la web del fabricante tenemos acceso a un SDK y a un repositorio de software… Hasta disponemos del código fuente de todo el firmware, aunque el fabricante te recomienda que no toques nada ya que de momento no existe una forma segura de actualizar el kernel o de recuperarlo (Y considerando el sistema de boot de los Intel PX, que parece diseñado por un borracho en una fría madrugada –absurdamente complejo, estrambótico y adoleciendo de diez mil chuminadas indocumentadas y repajoleramente tiquismiquies-, mejor no tocarlo y hacerle caso, por una vez, al fabricante).

En fin, que se trata de un producto útil pero muy verde, que adolece de algunos errores ergonómicos, y cuyo firmware necesita de varios –muchos- hervores más (aunque lo que funciona, funciona bien).

Por fin, tras casi tres semanas de espera, hace como cosa de veinte minutos que la gente de UPS me acaba de entregar el ILIAD. Como en las instrucciones dice "dejar cargar más de tres horas", me viene bien. Son las 13:30, como mi hora de fin de trabajo son las 18:00 ó 18:30, me quedan unas cuatro horas antes de empezar a jugar con él.

Pero por una vez en mi vida he resistido la tentación y lo he abierto echando fotos. Allá van:

ARRIBA. El empaquetado tal y como viene de la casa. El chico que entrega ya me conoce de sobra, ya que lo que viene de Holanda de mi curro también me lo entregan por UPS.

ARRIBA: Lo que hay dentro. La caja real del ILIAD.

ARRIBA: La parte trasera de la caja.

ARRIBA: Lo que hay dentro de la caja. En el precinto no se ve, pero en el canto hay una leyenda que dice qué parte va arriba y qué parte abajo.

ARRIBA: El "parato" con una bolsa mierdosa de transporte. La bolsa va dentro de la parte izquierda, dentro del cartón.

 

ARRIBA: Y finalmente todo lo que viene dentro. Dos hojillas con las condiciones de garantía, otras dos con las teclas rápidas en gabacho francés y pitinglis, un desplegable con las instrucciones a seguir, bien pobre manual porque al parecer el bueno va dentro del aparato. A la izquierda del todo un "sinseñor sin patas" que se conecta al ILIAD por un conector plano como el de los teléfonos modernos y que no me gusta un pelo (y que sirve de adaptador para el cargador, el cable USB y el Ethernet), abajo a la izquierda una espuerta de adaptadores de conexión a la red eléctrica (el europeo, el inglés, el chino y otro que desconozco, me imagino que americano), en el centro abajo del todo, un cable USB normal y corriente, y a la derecha el cargador.

Una cosa curiosa: en la pantalla, si uno la mira con detalle, puede ver cómo está marcado el menú que traerá el cacharro en cuanto se encienda. Imagino que es un resto estático del e-paper (y me da una idea para un posible fallo de seguridad de estos aparatos, je je).

Y eso es todo. Cuando haya jugado con él, comentaré más cosas.

La pregunta del millón es si seré capaz de aguantar hasta las seis de la tarde…

Constructores de copia    
Continuando con el tema de los constructores y toda su parafernalia vamos a ver por encima ciertos aspectos que no suelen ser muy tenidos en cuenta en los libros sobre C++.

Un constructor de copia es aquel que crea un objeto a partir de otro ya existente. En C++ el compilador se encarga de crear un constructor de copia si el usuario no lo ha hecho así, pero para C++/CLI es necesario y obligatorio que nosotros definamos uno si vamos a utilizar esta característica en nuestros objetos.

El motivo más importante por el cual esta característica es así se debe a que cualquier clase siempre hereda por defecto de Object, y esta clase no implementa dicho constructor, ya que el sistema podría no saber qué copiar y qué no copiar. Esto es así porque todos los objetos .NET son polimórficos ya que son accedidos mediante un manejador y entonces el compilador no sabe si implementar copia en superficie o en profundidad, y hasta qué nivel de la misma.

Por todo ello, si queremos crear un objeto a partir de otro, como en

Cosa ^c=gcnew Cosa(otraCosa);

tenemos que implementar dicho constructor. También tenemos que darnos cuenta de que al implementar este también debemos implementar el constructor por defecto ya que el compilador de C++/CLI no lo hará por nosotros al haber definido otro. Una forma de hacerlo sería mediante de la siguiente plantilla:

ref class Cosa
{
public:
    Cosa(void){}
    Cosa(const Cosa ^){…}
};

Ahora ya podemos hacer uso del código que hemos mostrado en primer lugar.

Pero C++/CLI también soporta semántica de pila para objetos referencia, por lo que si utilizamos el código siguiente veremos que no podemos compilarlo:

Cosa c;
Cosa ^otra=gcnew Cosa(c);

El compilador protestará con el error C3073. Una posible solución a ello es utilizar el código

Cosa c;
Cosa ^otra=gcnew Cosa(%c);

que mediante el uso del operador unitario % nos devuelve un manejador al objeto deseado. Aunque no es una solución elegante, es funcional y perfectamente válida.

Otra forma sería implementar un constructor de copia que tomara una referencia al objeto en cuestión:

ref class Cosa
{
public:
    Cosa(void){}
    Cosa(const Cosa ^){…}
    Cosa(const Cosa %){…}
};

De este modo ya no tenemos necesidad de utilizar el operador unitario y nuestro código queda mucho más elegante. Pero ahora caemos en dos nuevos problemas.

El primero de ellos es la duplicación de código; es decir, tenemos dos lugares diferentes en los cuales realizamos una construcción de copia, lo que en general viene desaconsejado por la propia idiosincrasia del C++. Una solución algo menos elegante sería el que ambos constructores llamaran a un método compartido por ambos (recordemos que un constructor no debe llamar a otro, por lo que la solución de llamar al de copia por referencia desde el de copia por valor no es válida). Otro tema es que mediante el uso de un método intermedio no podemos utilizar listas de inicialización.

El segundo es un poco más serio en lo que respecta a la filosofía .NET, y consiste en que el segundo constructor de copia no será visible para ningún otro lenguaje .NET. Aunque realmente no supone ningún problema ya que simplemente no estará visible –no romperá nada ni afectará en ningún modo a otro código), por lo que podremos utilizarlo desde cualquier otro código en C++/CLI pero no, por ejemplo, desde C#.

Cualquiera de las dos soluciones es perfectamente válida, y queda a decisión del lector utilizar la que quiera, sin olvidar que la primera de ellas (utilizar el operador unitario %) no significa ninguna penalización en el rendimiento del ejecutable.

Construcción mediante asignación    
Veamos el siguiente código:

Cosa c1;
Cosa c2=c1;

Sí se compila con C++ estándar no es necesario que definamos nuestro propio operador de asignación, pero por los mismos motivos que en C++/CLI no se autoimplementa el constructor de copia, tampoco lo hace el operador de asignación.

Pero aquí debemos distinguir entre copiar un manejador y copiar un objeto, es decir, el código siguiente es perfectamente válido y no requiere la definición de un operador de asignación, ya que lo que se está copiando es el manejador y no el objeto en sí:

Cosa ^c1=gcnew Cosa();
Cosa ^c2=^c1;

Por lo tanto, la definición del operador de asignación se hace de la misma forma en C++/CLI que en C++:

Cosa %operator=(const Cosa %)
{

}

Y dentro del cuerpo del método realizaremos las asignaciones que deseemos dependiendo del grado de profundidad con el que queramos realizar la asignación/copia.

Dado que los otros lenguajes .NET no soportan la copia de objetos referencia situados en la pila, dicho operador no estará visible para nadie más que para aquellos programas realizados en C++ y en C++/CLI. En otras palabras: como no podemos tener objetos referencia realmente situados en la pila, lo que el compilador de C++/CLI hace es darnos la ilusión de que están ahí, y por eso no tiene mucho sentido que lenguajes como C# o VB.NET puedan disponer de dicho operador.

Una última consideración a la hora de implementar este operador: ojito con la copia sobre sí mismo, que dependiendo de cómo la hagamos podría dejar el objeto en un estado no válido. Lo mejor es comprobar si la referencia de seguimiento obtenida es el propio objeto y en ese caso devolverse a sí mismo:

Cosa %operator=(const Cosa %c)
{
if(%c==this)
return *this;

}

Se puede obtener una versión PDF de este documento aquí

con 3 comment(s)
Archivado en: ,

Os voy a contar una historia, que comienza con un díalogo entre programador y cliente:

CLIENTE: Quiero que las opciones A y B no soporten esto.
PROGRAMADOR: ¿Una opción en la configuración?
C: No, que no se pueda cambiar.
P: Okis [Por dentro: Grrrrrr, una espuerta de ifs por todo el código, pero bueno].

El proyecto se termina, se testea, se ponen en la calle unas cuantas máquinas de muestra antes de entrar en producción a nivel nacional y...

C: Oye, que cuando dije Diego digo Ciego... Que quiero que todas las opciones soporten esto, incluidas la A y la B.
P: Grrrrrrrrrr. Si me hubieras hecho caso en un principio... te hubiera puesto la opción en la sección de configuración encriptada... Ahora hay que tocar el código y volver a verificar todo...
C: Ya, tu hazlo.

Okis. A quitar una espuerta de ifs... Pero mejor, que ya me conozco el percal, y lo mismo le da por querer que lo vuelva a poner. Compilación condicional. Si vuelve a cambiar de opinión, sólo tengo que borrar un define en las opciones del proyecto.

Se hacen los cambios pertinentes, lo pruebo en mi entorno virtual y todo OK. Se lo paso al cliente, lo prueba y el programa peta miserablemente y de la forma más extraña que pueda ocurrir... A mi no me ocurre. Me traen una máquina final, hago los cambios y no me ocurre. Al cliente sí y a mi no. Se detiene la producción en serie.

Vienen a mi casa a verlo: efectivamente, no falla. Pero en la calle sí. Traen otra máquina, la adapto y no falla. Se la llevan, la ponen en la calle y falla. La traen: no falla. Y mientras la producción detenida, unos diez tíos tocándose las bolas durante una semana...

La cosa sube a mayores. Me voy con ellos al local donde está la máquina de pruebas. La dejamos sola y nos sentamos en un rincón. La gente comienza a usarla: no falla. De repente, ¡pop, fallo! Salimos corriendo como lobos hacia el tipo que la estaba usando, le preguntamos qué ha hecho, nos dice que nada, que usar la máquina...

La abrimos. Miramos el cajón del dinero: billete de 5 euros encima de todo... Aísssss. Le damos su billete al cliente, que se va. Sacamos un billete de 5, probamos, y ¡peta!

Vale. Ahora ya está claro: desde el cambio nunca la he probado con billetes de 5, el valor más bajo ha sido de 10. Esas coincidencias de la vida en que cuando yo hice las pruebas tras el último cambio no tenía billetes de 5, y cuando he estado yo presente nunca se ha probado con un billete de 5... Pero la calle es la calle.

Ya termino. Anoche, revisión de código hasta las tantas. Ya la puedo hacer fallar en un entorno controlado... Vaya, un gráfico que no se carga. Pues sí que... Vaya, pues sí que se carga, en esta línea se carga... espera, no se carga. Cagontó. ¡Solucionado!

¿Qué estaba pasando?
Echemos un vistazo a este código (que es una especie de pseudocódigo que simula lo que me estaba pasando):

if(param!=Enum::Opcion1)
  
CargaGráfico(1);
CargaGráfico(2);
CargaGráfico(3);
CargaGráfico(4);

Simple, ¿no? ¿Y ahora?

#ifdef LIMITAR_AB 
if(param!=Enum::Opcion1)
#endif  
CargaGráfico(1);
CargaGráfico(2);
CargaGráfico(3);
CargaGráfico(4);

¿Lo ven? Pues yo cuando hice el código, no me di cuenta de ello. Si no está definido LIMITAR_AB, la sentencia con el if no se compila... Pero tampoco CargaGráfico(1);, esté o no definido LIMITAR_AB .

¿Por qué? Pues porque el preprocesador exige líneas completas, con lo cual ignora lo que venga después. Por lo menos en C++/CLI. Lo que no sé si se trata de un bug del compilador, de algo perfectamente definido en el estándar, o una de esas zonas grises que toda definición de un lenguaje tiene. Y también ignoro si ocurre en C#. Y tampoco tengo ganas de comprobarlo.

El código correcto sería:

#ifdef LIMITAR_AB 
if(param!=Enum::Opcion1)
#endif  
   
CargaGráfico(1);
CargaGráfico(2);
CargaGráfico(3);
CargaGráfico(4);

que ejecuta CargaGráfico(1); si no está definido LIMITAR_AB o, si estándolo, param no es la Opcion1.

Ahora estoy a la espera de llamadas con problemas que estoy casi seguro que no se van a producir... Aunque más de uno esté acordándose de mi por tener que trabajar, como poco, hasta mañana por la mañana. :-(

Vamos a tumbar el diseñador visual. Es muy sencillo. Creemos un proyecto Windows Forms en C++/CLI y pongamos en la ficha principal dos o tres componentes. Cambiemos entre vista de ficha y vista de código. Hasta ahora todo bien.

Ahora creemos un método como el ejemplo:

        void Hola(void)
        { 
        }

Volvamos a cambiar de vista de código a vista de ficha. Todo sigue bien.

Añadamos un nuevo método (sin borrar el anterior):

        int Hola(void
        { 
            return 3; 
        }

Ahora cambiemos de nuevo a vista de ficha.

¡Tachán!

Los controles han desaparecido. No importa lo compleja o sencilla que sea, los controles han desaparecido.

Volvamos a vista de código y comentemos cualquiera de los dos bloques añadidos.

¡Tachán!

Todo vuelve a la normalidad. Menos mal.

Antes de explicar nada, repitamos pero con ejemplo en C#.

Creemos una aplicación en C#, pongamos unos controles en la ficha y veamos el código. Añadamos lo siguiente:

public void Hola()
{
}

public int Hola()
{
 
return 3;
}

Y cambiemos a vista de ficha: no ocurre nada, la ficha se ve perfectamente.

Por lo tanto se trata de un bug de editor de fichas del Visual Studio trabajando en C++/CLI.

Explicación
Hemos cometido el mismo error en ambos proyectos, es decir, hemos definido dos métodos sobrecargados cuya única diferencia es el valor de retorno, y eso no se puede hacer ni en C# ni en C++ ni en C++/CLI.

Pero eso no quita que el diseñador visual falle. De hecho el de C# no lo hace, aunque el código sea realmente incorrecto.

Quien quiera jugar con el proyecto, lo puede obtener de aquí.

con 1 comment(s)
Archivado en: ,,

El concepto de clase lleva íntimamente asociado el concepto de constructor, tanto, que la primera no puede existir sin el segundo. Cuando obtenemos un objeto a partir de la definición de su clase correspondiente, se ejecuta el constructor adecuado que hayamos especificado, ya sea directa o indirectamente.

La relación constructor/clase es tan íntima que si el programador no especifica uno, el compilador creará uno por defecto que se encargará de inicializar todos los elementos de la clase con sus valores por defecto.

En C++ la instanciación (no me gusta nada la palabra, pero es la que consuetudinariamente se ha venido utilizando normalmente, así que es la que usaré) de una clase se realiza con el operador new, como se indica en el ejemplo:

Class Papel

{

int x,y;

public:

void Escribe(char *,int tam){…}

};

Papel *miPapel=new Papel();

La línea que nos interesa es

Papel *miPapel=new Papel();

El operador new pide al sistema operativo un bloque de memoria capaz de contener un objeto del tipo Papel y posteriormente ejecuta el constructor de la clase. Como en nuestro caso no hemos indicado uno, ha sido el compilador el que lo ha implementado por nosotros, asignando el valor cero a x e y (o más bien llamando a los constructores por defecto de los tipos agregados; en C++ sólo se llaman para las clases, no para los tipos nativos; sin embargo, en C++/CLI el constructor se llama para todos los elementos, ya que todos son clases, incluidos los que pretenden ser nativos –que realmente no lo son).

En C++ y C++/CLI, si indicamos cualquier tipo de constructor, el compilador ya no nos creará uno por defecto, y si queremos uno de ese tipo, tendremos que indicarlo nosotros mismos.

Como todo operador que se precie, new puede ser sobrecargado, tanto a nivel de clase como globalmente, para poder así ajustar a nuestro gusto la asignación de memoria. Volviendo a nuestro primer artículo, podríamos querer que para una clase las asignaciones fueran realizadas sobre el montículo global. Pues nada, sobrecargamos el operador para esa clase y realizamos las asignaciones sobre GlobalAlloc. De momento no vamos a profundizar más en la sobrecarga de new, ya que explicar lo básico seguro que llega para un artículo o dos de estas dimensiones. Simplemente comentar que se puede hacer.

Pero C++/CLI no nos permite sobrecargar su equivalente, gcnew. ¿Y por qué?, se preguntarán. Pues es muy sencillo: en el .NET Framework la política de asignación de memoria está predefinida y marcada por el motor de tiempo de ejecución. No podemos sobrecargar gcnew porque sólo se nos permite una forma de asignar memoria. Podría existir una vía para soslayar esto y es, utilizando las APIs para hospedaje de CLR, modificar dichos valores, pero en este caso ya no estamos operando con un gcnew sobrecargado, sino con las tripas del .NET (Quien quiera echar un vistazo rápido a dichas APIs, tiene un artículo de la MSDN traducido por mí y que puede leer aquí).

Conforme el lector va avanzando en estas explicaciones se va dando cuenta de que todas las decisiones de diseño que cambian el comportamiento de C++/CLI sobre C++ están perfectamente motivadas y justificadas. Quizás les gustaría echar un vistazo a los Fundamentos lógicos del diseño de C++/CLI de Herb Sutter(en donde se explican de forma razonada muchos detalles sobre la implementación de este lenguaje), pudiendo leerlo en castellano en otra traducción mía aquí.

***

Cuando trabajamos con variables situadas en el montículo, C++ trabaja casi igual que con variables alojadas en la pila, pero C++/CLI no. Y aquí debemos profundizar un poco en esto.

En C++/CLI existen dos tipos de clases. Los tipos-valor y los tipos-referencia. Los tipos valor se asignan en la pila. Los otros en el montículo. Los primeros heredan automáticamente de System::ValueType (y todos los tipos nativos lo hacen de esa clase). Los segundos heredan de System::Object (el que System::ValueType herede de System::Object es uno de los misterios sin resolver del CLI, ya trataremos eso en otro momento). Veamos un ejemplo.

//Tipo-valor

value class Complejo

{

int real,int imag;

};

//Tipo-referencia

ref class Complejo

{

int real,int imag;

};

El lector no observará ninguna diferencia salvo en la declaración, pero las hay, y no pocas. En un futuro artículo las trataremos. Ahora sólo nos interesa ver cómo trabaja el operador gcnew. Primero pongamos un ejemplo completo de cada tipo:

value class Complejo

{

public:

    int real,imag;

};

int main(array<System::String ^> ^args)

{

    Complejo c;

return 0;

}

ref class Complejo

{

public:

    int real,imag;

};

int main(array<System::String ^> ^args)

{

    Complejo ^c=gcnew Complejo;

return 0;

}

Veamos qué ha generado el compilador para el tipo-valor:

Y ahora para el tipo-referencia:

Curioso, ¿no?

Echemos un vistazo en primer lugar a la sección marcada como .locals. En ambos casos observamos que la segunda variable local es, en la primera imagen, un "valuetype Complejo c", y en la segunda, un "class Complejo c". Esos son los huecos que se van a reservar en la pila manejada. En el caso de la clase-valor, se trata del propio tipo, y en de la clase referencia, del manejador (o referencia, como queramos llamarlo).

También podemos observar cómo en el bloque de la instanciación de la clase por referencia cargamos el valor nullptr sobre el manejador de la misma, por lo que resulta redundante hacer nosotros mismos la asignación.

Y luego viene lo interesante de verdad. Un objeto-valor queda instanciado mediante la palabra reservada initobj. Y el objeto mediante newobj. Lo que nos indica que son cosas totalmente diferentes, aunque los pasos a seguir dentro de esas dos instrucciones sean más o menos iguales.

Primero se reserva memoria suficiente para contener el objeto. En el caso de la clase-referencia, el espacio ya está reservado en la pila. Luego se llama al constructor adecuado. Como no hemos especificado ninguno, el compilador ha creado uno por nosotros:

En la imagen podemos ver el tipo Complejo definido con su tipo, sus dos variables internas y el constructor citado. Más abajo vemos cómo se ha implementado dicho constructor, que al tratarse de un tipo trivial únicamente llama al de la clase padre.

En el caso del tipo por valor no hay constructor porque se trata de un tipo trivial que sólo implementa dos variables enteras:

Después de ejecutar el constructor o de asignar los valores adecuados, si todo ha ido bien, entonces se asigna el manejador a la variable correspondiente. Si se ha producido algún problema, el valor de la referencia será nullptr siempre y cuando hayamos controlado la excepción dentro del constructor. En el ejemplo, si hubiera algún problema dentro del constructor, no volveríamos a main(), ya que la excepción no controlada sería elevada al manejador global, y entonces nos daría igual el valor de la referencia, puesto que estaría inalcanzable.

Y ahora demos otra vuelta de tuerca. Crucemos los ejemplos (Se recomienda a los programadores de C# no realizar este tipo de cosas con sus herramientas, podrían entrar en La Dimensión Desconocida. Avisados quedan. J ).

value class Complejo

{

public:

    int real,imag;

};

int main(array<System::String ^> ^args)

{

    Complejo ^c=gcnew Complejo;

    c->real=33;

return 0;

}

ref class Complejo

{

public:

    int real,imag;

};

int main(array<System::String ^> ^args)

{

    Complejo c;

    c.real=33;

return 0;

}

Veamos qué ocurre si instanciamos un tipo-referencia en la pila, es decir, qué código genera la columna de la derecha:

¡No hay diferencia entre instanciarlo en la pila o el montículo! Efectivamente, el compilador de C++/CLI no engaña de malos modos. Por definición el .NET no permite que puedan existir objetos-referencia en la pila, pero como el C++ nativo no lo impide de forma alguna, los chicos de MS han hecho bien su trabajo y nos permiten trabajar con semántica de pila allí donde hay punteros. ¿No les esto suena de algo? Si, de hecho es casi equivalente a trabajar con las referencias de seguimiento que ya hemos visto. Cuando analicemos los destructores veremos qué significa todo esto para la destrucción determinista.

Ahora comprobemos el caso contrario (el bloque de código de la izquierda):

¡Buf, qué lío! Encajamientos, desencajamientos… esto merece un nuevo artículo, este también mostrando código en C#, ya que el tema es de un interés crucial para entender ciertos posibles cuellos de botella y caídas en el rendimiento general.

Se puede obtener una versión PDF de este documento aquí.

con no comments
Archivado en: ,

Visto que bajo el blog no es que se vean muy bien los ejemplos del código (y las pocas ganas que tengo de pelearme con el HTML), he decidido, aparte de publicarlos como entradas en el blog, convertir el fichero DOC original a PDF y guardarlo en disco, así que a final de cada entrada pondré un enlace para que quien quiera verlo un poco mejor formateado (o simplemente almacenarlo) se baje el PDF correspondiente.

De momento hay tres entradas:

Estos documentos no se suministran con licencia libre, sino con algo que yo mismo he llamado Licencia ZXL DOC. Si no quieres pinchar en el enlace, básicamente viene a consistir en que puedes hacer lo que quieras con el documento excepto apropiártelo, obtener dinero de él (ni siquiera por el soporte) ni modificarlo más allá de correcciones triviales. Y por supuesto se ofrece sin garantía de ningún tipo.

Bueno, pues eso, que hace un rato he recibido la comunicación de que he sido nombrado MVP de Visual C++.

Agradecer a Cristina y en general a los chicos y chicas de Microsoft el galardón, así como a aquellos que me han propuesto/votado o como quiera que funcionen las nominaciones, y a aquellos que directa o indirectamente han contribuido a ello.

Pues eso, que cuando descubra cómo poner el logo de MVP aquí, pues lo pondré.

Tambien comentar que el premio es una ayuda e inyección de moral a la hora de responder en los grupos de MS, mantener este blog y en general colaborar y llevar a cabo todas las actividades que realizo. ¡Gracias a todos!