
Para ver bien los detalles es mejor usar este pdf.
Información aleatoria proveniente de un ser racional
Después de hablar de las macros hace unos días, me he dedicado a investigar algo más sobre cómo funciona el preprocesador de C/C++. Está muy limitado, pero tiene algunas posibilidades.
Usando #if/#endif es posible seleccionar parte de código, pero no podemos usar #if/#endif dentro de una macro. Es decir algo como esto no funciona
#define SELECCIONA(a,b,c) #if a \
b \
#else \
c \
#endif
El motivo es que el preprocesador es muy tonto y sus comandos (que empiezan por #) sólo son reconocidos a principio de línea. Debido a que el \ a final de línea significa que se sigue en la siguiente línea como si fuera esta, la macro anterior sería equivalente a
#define SELECCIONA(a,b,c) #if a b #else c #endif
Por lo que ni el #if ni el #else ni el #endif son reconocidos como comandos de preprocesador.
La solución es algo enrevesada, pero funciona y es usada en boost::preprocesor. Se basa en el concatenador de tokens. Es un operador especial (no es un comando) del preprocesador. Se escribe ##. El resultado de esta concatenación es agrupar dos identificadores en uno.
#define CONCATENA(a,b) a ## b
CONCATENA(hola, adios) //Equivalente a escribir holaadios
Debido a que el preprocesador vuelve a expandir el resultado de sus macros, es fácil usar el siguiente truco para la selección.
#define IF_TRUE(t,e) t
#define IF_FALSE(t,e) e
#define IF(c,t,e) IF_##c (t, e)
IF(TRUE, bien, mal) //Se expande a IF_TRUE(bien,mal) y se vuelve a expandir a bien
Ahora podemos combinar este truco con la técnica explicada hace unos días para crear código opcionalmente.
El patrón de diseño memento es muy simple y bastante útil. Como todo en la vida, también tiene sus puntos negativos. Por esta razón existe otro patrón de diseño llamado command que soluciona esos problemas.
El patrón de diseño Memento
Supongamos que queremos hacer un sistema de Undo/Redo. Esto significa que, hagamos la acción que hagamos sobre un objeto que llamaré primario, hemos de tener la posibilidad de volver a un estado anterior. (Nota: Llamo acción a una operación que modifica el estado.)
El patrón de diseño memento resuelve este problema y es muy sencillo. Lo que hay que hacer es incluir en el objeto dos operaciones:
Estas operaciones no son acciones en el sentido de modificar el estado, incluso al recuperar el estado lo que hacemos es volver a un estado anterior, no modificar el actual.
La operación que guarda el estado lo que hace es generar un objeto con el estado encapsulado dentro. A este objeto se le llama el objeto memento (recuerdo). Este objeto no tiene operaciones y lo único que se puede hacer con él es pasarlo a "recupera estado". Al recuperar estado no se destruye el memento, sólo se usa para recuperar el estado.
De esta manera:
Sin embargo, el patrón memento tiene un gran problema. Siempre se guarda todo el estado y este puede ser muy grande. Si quisiéramos tener un sistema de Undo/Redo que recuerde muchos estados anteriores, el problema del almacenamiento de los estados llega a ser costoso en términos de uso de memoria.
Hacia un sistema de estados diferencial
La solución obvia es hacer un sistema de cambio de estados diferencial. En este sistema sólo se guardan los cambios entre dos estados. De hecho, la operación "Undo" suele deshacer únicamente una acción cada vez. ¿Por qué guardar cambios de estado completos cuando sólo es una acción la involucrada?
Ideemos entonces un sistema que por cada acción genere un objeto "minimemento" que indique cómo se deshace esa acción desde el estado actual. Llamaré a estos objetos átomos.
Ahora las dos operaciones del objeto primario serían:
Gráficamente sería así:
He sido cuidadoso en distinguir los objetos que existen en memoria (son los sombreados en azul) y los que existieron pero que ahora no existen (los de relleno blanco).
Este pequeño cambio que hemos hecho conlleva una gran pérdida. Ahora no tenemos objetos memento y si queremos pasar del estado 0 al estado 3 no basta con usar el átomo C. Tenemos que usar los átomos A, B y C en ese orden. De hecho, si intentásemos usar el átomo C sobre el estado 0 tendríamos un resultado inesperado ya que los átomos sólo deshacen una acción. En concreto, el átomo C la acción que va del estado 3 al 2. Por esa razón, deshacerla tiene que ser forzosamente del estado 2 al 3. En definitiva: al usar un sistema diferencial se añade una precondición que no existía en la operación de "recuperar estado" con el patrón memento.
En principio, no pasa nada ya que de todas formas podemos volver a cualquier estado anterior y, con un pequeño cambio, posterior.
Rehaciendo estados
Supongamos que hemos aplicado con éxito los átomos A, B y C de manera que hemos vuelto al estado 3. Como ocurría con el patrón memento, esto no significa que hayamos destruido los átomos A, B y C. De hecho, podríamos usarlos para volver a aplicar las acciones y llegar de nuevo al estado 0.
Esto sólo implicaría tener que añadir una nueva operación de realizar "redo" usando un átomo.
El lector astuto habrá visto que lo que hacen los átomo es reificar las acciones. Es decir, las acciones son objetos ahora (acción=átomo). Esta idea es la que subyace al patrón de diseño command.
Existe realmente un pequeño cambio para llegar al patrón de diseño command. Nosotros venimos del patrón memento y es el objeto primario el que tiene las operaciones "undo" y "redo". En el caso del patrón command estas operaciones están en el propio átomo que guardará internamente una referencia al objeto primario.
Las ideas clave del patrón de diseño command son:
Me he encontrado con el problema de, dado un número entero, calcular la siguiente potencia de dos. Por ejemplo, del número cinco obtendría ocho. Este problema es muy usual. La restricción de ser potencia de dos aparece en muchos algoritmos. Por ejemplo, en el cálculo de FFT.
Hay un algoritmo bastante curioso para resolver este cálculo. Es el que sigue:
n = n - 1;
n = n | (n >> 1);
n = n | (n >> 2);
n = n | (n >> 4);
n = n | (n >> 8);
n = n | (n >> 16);
...
n = n | (n >> (bitspace / 2));
n = n + 1;
Depende del tamaño de palabra que usemos para almacenar los enteros y no funciona en valores menores o iguales que cero. De todas las maneras, hace lo que tiene que hacer de forma maravillosa.