Funciones variádicas permiten un número arbitrario de parámetros de tipo arbitrario. La más conocida en C y C++ (y otros lenguajes) es printf. Aquí tratamos de cómo declarar e implementar una función variádica. También comparamos estas funciones con otros conceptos relacionados como sobrecarga de una función y patrones variádicos (variadic templates).

Cómo declarar una función variádica

Cómo ejemplo de declaración usamos la signatura de printf.

int printf(const char* formato, ...);

La signatura de una función variádica tiene como parámetro una elipsis .... Esta elipsis quiere decir: y unos cuántos parámetros más de tipo desconocido. A continuación llamaremos los valores que el elipsis representa “parámetros anónimos”. Son parámetros que existen, pero para cuales no existe un nombre con que los podemos referenciar.

Una elipsis siempre tiene que ser el último parámetro con excepción de parámetros con valor de defecto.

int mi_función_variádica_1(int a, ..., int b);  // Error de compilación
int mi_función_variádica_2(int a, ..., int b = 0);  // Ok

Los parámetros con nombre y sin valor por defecto son parámetros obligatorios. Obligan al usuario de especificar al menos uno, dos o más valores. Vamos a ver en la implementación que un parámetro obligatorio como mínimo es útil.

Cómo implementar una función variádica

Cuando se llama a una función variádica, el compilador guarda todos los parámetros en el stack. El problema es como accedemos a estos parámetros ya que no tienen nombre. La solución viene por unas macros cuya implementación depende tanto de cada compilador que recomiendo no intentar entenderla a menos que sea este tu destino.

Explicamos las macros en un ejemplo de uso. Nota que las macros vienen sin el namespace. Macros nunca tienen namespace.

// Incluye la definición de las macros
#include <stdarg.h>

// Una función variádica
void trata_valores(const std::list<id_de_tipo> lista_de_tipos, ...)
{
    // Una referencia para aceder a los parámetros anónimos
    va_list parámetros_desconocidos;
    
    // Asigna el puntero de lectura al primer parámetro anónimo.
    va_start(parámetros_desconocidos, lista_de_tipos);
    // El segundo parámetro es el último parámetro con nombre.
    // Es una de las dos buenas razones de tener al menos un
    // parámetro con nombre, porque sin él no se puede inicializar
    // la referencia de parámetros anónimos.
    
    while (/* ver explicación abajo */)
    {
        // Obtiene un parámetro. ¿Cómo sabemos su tipo?
        un_tipo i = va_arg(parámetros_desconocidos, un_tipo);
    }
    
    // Limpia cualquier recurso reservado por va_start y va_arg
    va_end(parámetros_desconocidos);
}

En corto:

  1. Define un nombre con va_list.
  2. Inicializa el acceso a los parámetros anónimos con va_start.
  3. Obtén un nuevo parámetro con cada llamada a va_arg.
  4. Recoge la basura con va_end.

La dificultad es ¿cómo saber los tipos de los parámetros? Pues, no se puede por las macros de acceso. La signatura de una función variádica no asegura ningún tipo específico para los parámetros anónimos. De hecho, una implementación puede obtener diez valores de tipo int y tres de tipo float con éxito aunque el usuario no haya llamado la función variádica con estos parámetros. La macro va_arg simplemente devuelve un valor del tipo especificado en el segundo parámetro y mueve el puntero de lectura tantos bytes como el tamaño de este tipo. Es genial para leer memoria basura, pero ¿cómo podemos obtener los parámetros reales?

Pues, ahí viene la segunda razón para el primer parámetro obligatorio: el primer parámetro contiene una información sobre los tipos de los parámetros anónimos. Por esto lo hemos definido como una lista de identificadores de tipos en el ejemplo arriba. Podemos imaginarnos que la lista contiene tantos elementos como parámetros anónimos y que cada valor de id_de_tipo corresponde a un tipo posible.

En la función printf, esta lista de tipos de parámetros es el parámetro de formato. El bucle while dentro de la implementación de printf imprime cada letra a menos que viene el símbolo %. Entonces sabe que viene la especificación de un tipo que entonces puede usar para obtener uno de los parámetros anónimos. Obviamente debe haber tantos símbolos % en el texto de formato como hay parámetros anónimos – y los tipos especificados deben corresponder a los reales. Sino printf imprime una basura que cualquier programador seguramente ya ha visto en sus vida.

Limitaciones de funciones variádicas

Ya hemos visto que funciones variádicas no aseguran el tipo de los parámetros anónimos y que esto puede llegar a no obtener los valores de los parámetros correctamente. Esto, desde luego, no es aceptable para un buen estilo de programación y, por eso, mucho se ha ideado en C++ para evitar tener que usar funciones variádicas. No obstante la función printf es junto con las macros de C una de las chapuzes más populares que se siguen usando porque simplemente permiten hacer algo complicado de una forma simple.

Otra limitación es el número de tipos que una función variádica puede manejar. Cada implementación contiene un bucle while, y dentro de esto habrá un switch que llama para cada caso a va_arg con un tipo diferente. Lógicamente no se puede manejar un tipo si no está programado en este switch. Esta es la razón porque printf sólo acepta tipos simples, nativos del lenguaje. Los autores de printf simplemente no podían saber qué estructuras a imprimir inventarás.

Alternativas a funciones variádicas

Con tanto peligro de leer mal los parámetros, C++ tenía que ofrecer nuevas maneras de pasar un número de parámetros arbitrario. No obstante, todos tienen limitaciones.

Sobrecarga y valores por defecto

Si el número de posibles permutaciones de parámetros no es demasiado alto, se pueden definir sobrecargas de una función para cada una. Esto se hace, por ejemplo, en el

operator<<(std::ostream& out, const un_tipo& valor)

Hay una definición para cada tipo nativo del compilador y cada clase de biblioteca estándar cuyo contenido tiene sentido de imprimir como para std::string.

Aunque estés depuesto a escribir mil sobrecargas de una función, no llegarás a sustituir printf con ellas. No obstante, puedes usar algunos trucos.

  • Si lo que varía es más el número de los parámetros y no su tipo, entonces te basta una función con muchos parámetros del mismo tipo y todos estos parámetros tienen valores por defecto. El valor por defecto entonces dirá algo como “parámetro no especificado”. Sin embargo, esta versión puede ser más lenta que una función variádica, porque el programa guarda todos los parámetros en el stack – también aquellos que no se usan.
  • El truco del operator<< consiste en sólo permitir un parámetro de tipo variado y luego encadenar un número arbitrario de operadores. De esta forma sólo hay que definir tantas funciones como hay tipos a manejar.
  • Si lo que varía son los tipos y no el número de parámetros, entonces se pueden usar templates de funciones.
Templates

Un template permite definir una función con tipos variados. Esto permite en principio crear una implementación que vale para cualquier tipo, porque se puede usar el nombre del tipo variable en la macro va_arg.

Un template convencional tiene un número de parámetros fijo. Desde el estándar C++11, templates pueden también tener un número variado de tipos. Esto se llama variadic template o parameter pack. Cuando se tiene un número arbitrario de tipos, entonces la función puede aceptar también un número arbitrario de parámetros. De esta manera, sí, se puede escribir un sustituto de printf que asegura los tipos de los parámetros. (Para facilitar el manejo de una lista de parámetros de tipo arbitrario, la biblioteca estándar provee el tipo std::tuple.)

En una función de tipos y parámetros arbitrarios habrá, igual como en una implementación de una función variádica convencional, un bucle, que hace algo para cada parámetro: típicamente llama a una función con sólo un parámetro y luego vuelve a llamar a si misma con los parámetros restantes. Es decir, funciones de variadic template suelen ser a menudo recursivos. Esto les hace lento, ya que tienen copiar los parámetros varias veces. Además, se genera una instancia de la función para cada conjunto de tipos. Por eso, una función con templates variádicos provee la misma forma de uso, pero no ejecutará tan rápida como una función variádica sin templates.

Conclusión

Funciones variádicas como printf permiten implementar funciones con un número de parámetros arbitrario de forma eficaz. Desgraciadamente dependen de que el usuario provee una lista con los tipos de los parámetros suministrados. Al hacerlo mal, una función variádica puede leer o incluso escribir en la memoria de forma no controlada.

C++ provee los variadic templates como alternativa que asegura los tipos de los parámetros, pero sus implementaciones suelen traer desventajas a la hora de ejecución. Además, su implementación tampoco suele ser más fácil. Por eso, las funciones variádicas tienen su derecho de existencia, aunque un buen programador siempre pensará en alternativas antes de implementarlas.

Lectura adicional

Ya desde tiempo, los “métodos ágiles” de desarrollo de software como Scrum, eXtreme Programming o Scaled Agile son de moda; tanto que incluso tu empresa ya estará disfrutando de ellas. O de algo parecido al menos de nombre. Porque los métodos ágiles se parecen a menudo al comunismo. Son ideas maravillosas mientras no tienes que vivir con ellas.

Lo que también tienen en común con el comunismo, es que ciertos elementos, sí, son útiles. Igual como un capitalismo social avanza el bienestar general (capitalismo = hay medicamentos, socialismo = los enfermos pueden pagarlo), elementos de los métodos ágiles pueden mejorar mucho el desarrollo de software. La pregunta es: ¿qué elementos?

Para sacarles el mejor rendimiento, conviene entender el contexto cultural de donde los métodos ágiles salieron. Como muchas ideas religiosas recientes, los métodos ágiles también han salido de los Estados Unidos de América. Y como los norte-americanos tienen un don de marketing, les gusta dar nombre llamativos como “Scrum”, “eXtreme Programming” o “Chrystal Clear” que, para el tu bien mental, mejor no traduzques a tu lengua materna. El libro gordo de esta religión se llama “Agile Manifesto” y expresa, entre otras cosas, y en palabras más positivas, los siguiüente puntos en negrita.

  1. La profesionalización del concepto “el producto madura en el cliente”. Funciona muy bien, mientras el cliente piensa que las frecuente actualizaciones corresponden a “mantener el producto” y no a “no hacerlo bien desdel principio”. Madurar el producto en el cliente está aceptable para el software que tienes en tu teléfono, pero no suele ser buena idea para aparatos medicinales.
  2. Nadie tiene dominios. Todos programan de todo. En todas las empresas (menos en una en que era el único programador) se ha tenido la idea de un proxy que sería capaz de hacer tu trabajo si no estás por enfermedad, vacaciones o despido. Y en ninguna empresa ha durado mucho hasta dejarlo. Los programadores no son intercambiables. Todos no saben igual de todo. En el momento que escribes una pieza de código, eres automáticamente el máximo experto para ello. Es inevitable. Así, o bien aceptas que los demás programadores constantemente deben aprender código nuevo que les hace bastante lento, o dejas mentirte y aceptas que cada uno tiene un dominio. No quiero decir que vale la pena mandar a todos a hacer de todo. Puede funcionar si se hace con consecuencia desde el principio. Pero no pondrás un novato a estudiar una nueva parte de código por varios días, si mañana tienes la release final.
  3. No hagas documentación que nadie se lea. Porque cualquier documentación tiene el peligro de hacerse obsoleta con los cambios en el códio. Por eso es mejor no tenerla. En cambio el códio es auto-explicativo y el resto se pregunta. No olvides que comunicación es importante en los métodos ágiles. Ya sabes, todos deben saber de todo, y mejor que no haya nadie a quién ocurre proponer un documento que explica el programa al menos a alto nivel. Saber leer y dejar trabar a los demás es para introvertidos. Los novatos tienen que integrarse y esto es más fácil si deben preguntar de todo. Mejor aún si deben pasarse una semana entera (o para siempre) con una pareja veterana en la mesa. Así y comunicación.
  4. Usa el sentido común. Recordar que comunicación es importante parece poco novedoso si vienes de un país con un clima en que a la gente gusta encontrarse en la calle, pero esto quizá no es tan obvio para otros países donde tienes el permiso de sacar un arma si alguien pisa tu territorio. Tampoco falta un título en bla-bla avanzado para descubrir, que es más eficiente si un programador termina un trabajo antes de empezar uno nuevo en lugar de interumpirle constantemente. Si quieres promover métodos ágiles en tu empresa, considera que, fuera de Estados Unidos, la gente viene equipado con sentido común desde fábrica y probablemente ya lo sabe.

De hecho, sentido común ayuda considerablemente en sacar el máximo provecho de los métodos ágiles. Desgraciadamente, estos “consultores” de métodos ágiles demasiadas veces no son lo suficientemente ágiles ellos mismos para reconocer, que los prerequisitos de su método son díficiles de conseguir. Programadores no son fáciles de intercambiar, el product owner no tiene tiempo aunque debería y el cliente no tiene ganas de pasar su tiempo en la empresa que paga para quitarle el trabajo.

Un buen ejemplo del carácter religioso de métodos ágiles es la creencia de la mayor eficiencia de la programación en pareja. Una minoría de los programadores se llevará bien, y pasa el día charlando en lugar de trabajando. La mayoría acaba estando tan estresando con tener constantemente otro tipo al lado, que se marchará de la empresa. De hecho, la programación en pareja es una manera eficaz de descubrir los programadores competentes: son aquellos que se han ido tras un año. Pero también puedes verlo positivo: siempre tendrás a un equipo joven. (Si no te dejan introducir la programación en pareja para descubrir a los talentos, obligar a llevar corbata funciona también. Si ya llevas corbata pero no entiendes el chiste, entonces, enhorabuena, ya trabajas para tu empresa perfecta.)

No sigas a un método ágil porque es la moda. Busca los elementos que mejoran tu desarrollo y aplícalos al dominio adecuado.

Como ejemplo de limitar el dominio adecuado, considera una empresa que vende equipamento médico. No puede vender su producto mediamente hecha y esperar mejorarlo cada dos semanas con las opiniones de sus clientes. Un laboratorio médico puede tardar un año entero probando que tu producto funciona como deseado. Te puedes imaginar como se alegran de poder repetirlo cada mes. Pero lo que puedes hacer, es producir versiones tempranas durante el desarrollo del producto para poder probarlo con todos los componentes existentes juntos. En este caso el método ágil no funciona con el cliente externo, pero, sí, con clientes internos.

No obstante, son muchos los elementos de los métodos ágiles que valen la pena tener en consideración.

  1. Empieza con un producto minimal, pero útil y con calidad. Lo “minimal” puede a veces ser todo, pero muchas veces no lo es. Y si lo poco funciona bien, los clientes estarán interesados en la próxima versión.
  2. A nivel de equipo de desarrollo, esto se traduce a generar una versión temprana para que otros equipos puedan integrarla. Puede ser incompleta, pero debería funcionar.
  3. Genera versiones con frecuencia, para que los demás equipos pueden tener la mejor versión posible en cada momento.
  4. Intenta dividir tareas grandes en tareas pequeñas pero completas en sí, para que puedas publicar algo en cada iteración.
  5. Usa ramas de desarrollo para añadir nuevas funcionalidades y siempre manten una versión que funciona. Puede ser tu rescate si un desarrollo no funcionaba.
  6. Sólo añade cambios completos. Completo quiere decir que haya casos de pruebas (que se ejecutan automatizados) y la documentación adecuada. (Actualizar la documentación es parte del cambio.)
  7. Limitarse a lo que es necesario. Lo necesario es a veces no tan fácil de reconocer. En primer lugar no es crear un vector de n elementos especificado para el caso n = 3, si sólo necesitas un vector de tamaño 3. En el mismo primer lugar es no crear documentación porque sí, sino por qué ya sabes denominar alguien, que la quiera leer. (Miembros nuevos del equipo, por ejemplo.) De otro lado hay decisiones en que hay que pensar hasta el final: La arquitectura del programa, los requisitos mínimos, si los programadores serán intercambiales (y por lo tanto se les permite más tiempo para aprender código nuevo), la estructura de crear y testear versiones etc. Una buena estrategia para limitarse a lo necesario es pensar: ¿ya tenemos un escenario en que esto va a importar? ¿Sí? ¡Entonces hazlo! ¿No? ¡Entonces espera con ello! No hagas cosas por si acaso sino porque alguien quiere pagar por ellas.
  8. Fomentar la comunicación. Las pequeñas reuniones diarias y un tablón con las tareas pendientes fomenta que todos ven como va el equipo en completo.
  9. Ten una estructura de comunicarse con el cliente. Los métodos ágiles demandan que los clientes están presentes en el equipo de desarrollo. Esto es muchas veces imposible. Pero debería haber alguien que pueda presentar la vista del cliente – y esto será preferiblemente uno que entiende realmente a clientes aunque no entienda de todo a los programadores.
  10. Dejar a los programadores estimar cuánto tiempo necesitan para una tarea y dejarles decidir, a cuantas tareas se quieren cometer. Tendrás una mejor estimación de tiempo y los planes se basan en las palabras de aquellos que deben cumplirlas. Es decir, en lugar de “esto necesito hasta viernes” preguntar “qué necesitas pra terminarlo hasta viernes”.
  11. No cambies planes durante una iteración. Deja suficiente tiempo libre para atender a emergencias típicas.
  12. Intenta terminar el plan de una iteración. Si un programador ya hizo todo su trabajo, entonces quizá puede ayudar a otro en crear casos de pruebas o actualizar la documentación.

En fin, esto no es un pamfleto contra los método ágiles, pero uno contra su aplicación religiosa. Y un recordadorio que todas las culturas tienen puntos fuertes. Hazte conciente de ellas antes de adoptar algo sólo porque aparenta tener éxito en EEUU.

Lectura adicional

En C++ existen varios modificadores de tipos de variables. El más conocido es const. Aquí queremos hablar de un modificador más oscuro que const: volatile.

El modificador volatile quiere decir: Esta variable no sólo modifica tu programa sino alguien más.

La pregunta es: ¿quién más cambia las variables de tu programa? Pues, un componente de hardware, por ejemplo. Cuando escribes un controlador de hardware, entonces usas volatile bastante a menudo.

Los dispositivos de hardware se “mapean” (asignan) a ciertas direcciones de memoria. Por ejemplo, en la dirección 100 no escribes en RAM, pero en un registro de la tarjeta gráfica. Si escribes ahí, digamos, un cinco, entonces la tarjeta gráfica lo entiende como una orden de poner la pantalla en negro. El hardware real es ligermanete más sofisticado, pero esto es el principio: ciertas direcciones de memoria corresponden a registros de dispositivos de hardware.

Lógicamente debes asegurar que tu programa realmente escribe en esta dirección. Y que también realmente lee desde ahí. Porque si una variable corresponde a una dirección donde la tarjeta gráfica devuele un valor de error, entonces no te puedes fiar del último valor que esta variable tenía en tu programa. Debes realmente leerlo desde ahí. Y esto es la tarea del modificador volatile. Dice al compilador que, por favor, no optimice el uso de esta variable.

Y ahora: ¿Por qué tu programa no siempre lee y escribe en la posición de memoria a que la variable corresponde?

Esto tiene que ver con la optimización del código. Considera el siguiente trozo de código.

const int a = 5;
int b = a + 3;
b = 4;

Sin optimización, la constante a corresponde, digamos, a la dirección 8. La constante b a la dirección 12.

  1. Si el compilador no optimiza, entonces traduce la primera línea en un comando de escribir un 5 en la dirección 8.
  2. La segunda línea traduce en leer lo que pone en la dirección 8, sumarle 3 y escribirlo a la dirección 12.
  3. Luego escribe un 4 en la dirección 12.

No obstante, esto se puede optimizar para ganar velocidad. La constante a corresponde a un 5. Así podría modificar la segunda línea en.

int b = 5 + 3;

o directamente en

int b = 8;

Como se escribe el valor 4 en la variable b directamente después, entonces el compilador podría borrar las dos primeras líneas. El resultado es el mismo.

A menos si las variables a y b corresponden a direcciones de dispositivos de hardware. Para ellos, sí, importa que se lee desde la dirección que corresponde a la constante a y se escribe en la dirección de b. El mismo programa con volatile puede tener un significativo bastante distinto.

const volatile int a = 5;
volatile int b = a + 3;
b = 4;
  1. Declara un campo de lectura del despositivo y asígnale el valor por de defecto 5.
  2. Lee el nuevo valor de a, súmale 3 y escríbelo en la dirección de b.
  3. Escribe un nuevo valor en la dirección de b.

Bajo este punto de vista tiene también sentido de escribir dos valores distintos en b sin utilizar el valor en el programa. Lo puedes entender como un comando que ejecuta, por ejemplo, la tarjeta gráfica y no tu programa.

Pero no olvides, todo esto sólo tiene efecto si el compilador optimiza el programa como lo suele hacer para una versión de release.

Por cierto, si tienes la paranoia que cualquier variable se podría cambiar desde fuera, entonces no necesitas declarar todas volatile. Basta compilar tu programa sin optimizaciones – es decir, en modo de depuración.

Lectura adicional

En C++ existen varios modificadores de tipos de variables. El más conocido es const. Aquí queremos hablar de un modificador que de alguna forma es el contrario de const: El modificador mutable.

El modificador mutable quiere decir: Este miembro sigue siendo variable aunque la instancia de su clase sea declarada const. Fuera de una clase, mutable no tiene sentido.

La pregunta es: ¿por qué alguien quiere tener un miembro de clase variable cuando ha declarado el objeto const? Vemos un ejemplo clásico de la programación multi-hilo.

class Lista
{
public:
    // Una función que modifica la instancia
    void añadeElemento(const int nuevo_valor)
    {
        while (mutex)
        {
            // Bloquea el objeto para acceso exclusivo
            mutex = true;
            // Modifica el objeto
            una_lista.push_back(nuevo_valor);  
            // Libera el objeto
            mutex = false;
        }
    }
    
    // Una función que lee la instancia
    std::list<int> devuelveLista() const
    {
        std::list<int> copia;
        while (mutex)
        {
            // Bloquea el objeto para acceso exclusivo
            mutex = true;  
            // Crea una copia temporal
            copia = una_lista;
            // Libera el objeto
            mutex = false;
        }
        // Con la copia el hilo puede leer cuanto quiera
        return copia;  
    }
    
private:
    std::list<int> una_lista;
    mutable bool mutex;
};

La clase Lista contiene un objeto complejo una_lista y un mutex. En un entorno multi-hilo, todos los hilos podrían querer acceder a la lista potencialmente al mismo tiempo. No hay problema mientras todas los hilos quieren leer, pero esto se cambia si uno quiere escribir. Añadir un nuevo elemento requiere cambiar varias variables dentro de la lista. Con un poco de mala suerte (no mucha), un hilo intenta leer de la lista cuando el cambio todavía no está completo y obtiene así datos corruptos. Por esta razón, se bloquea la lista con un mutex. (Una tipo de mutex no muy sofisticado, pero para nuestro ejemplo nos sirve.) Mientras un hilo tiene el mutex (asignando true, los otros se deben esperar en el bucle while.

Si un hilo quiere leer la lista con la función devuelveLista, entonces, conceptualmente, lo puede hacer sobre un objeto constante. Leer no modifica el contenido del objeto y, en consequencia, la función devuelveLista está declarada const. No obstante, el miemro mutex también sería constante en este caso. Y ahora, ¿cómo se puede bloquear la lista contra el acceso de escritura en una función const?

La solución es declarar el mutex mutable. Así el mutex sigue siendo variable mientras el resto de la clase es constante.

Es obvio que sobrescribir el carácter constante de un objeto con mutable invita a crear un mal diseño. Por eso úsalo con consideración. Un miembro mutable suele contener meta-información como un guardian de acceso (como un mutex), o un contador que cuenta cuántas veces se ha leído un dato. Los datos mismos nunca tienen razón de ser mutable.

Lectura adicional

En C++ existen cuatro formas nativas de convertir tipos: static_cast, const_cast, reinterpret_cast y dynamic_cast. Además, se puede convertir un tipo con un constructor de clase o sobrecargar el operador de cast. Finalmente existe el cast tradicional de C. Aquí queremos hablar de las diferencias que tienen y cuando conviene usar uno u otro.

Lista de diferentes cast

Para aclarar un poco más las diferentes formas, empezamos con una enumeración de las diferentes posibilidades.

Formas de conversión de tipos
Nombre Ejemplo Uso típico
Cast tradicional de C (int) float_var Combina static_cast, const_cast y reinterpret_cast
static_cast static_cast(float_var) Convertir tipos nativos o clases con constructor o operador de cast
const_cast const_cast(const_int_var) Eliminar attributos de tipo como const
reinterpret_cast reinterpret_cast( void_pointer) Pretender que un tipo (de puntero) es realmente otro
dynamic_cast dynamic_cast( referencia_a_clase_base) Reinterpretar una clase base como una clase derivada
Constructor con un parámetro Clase(const OtroTipo& fuente) Inicialiazar una instancia con otro tipo
Operador de cast operator int(void) const; Convertir una instancia a otro tipo

Cast tradicional de C

El cast tradicional consiste en escribir el tipo entre paréntesis delante la variable que quieres convertir.

int a = 5;
float b = (float) a;

Esta forma de cast puede actuar como muchas de las otras formas de cast. El problema es que no siempre está claro cuál. Por eso es posible que tu programa acaba haciendo otra cosa que el esperabas y, por eso, suele haber la recomendación de ya no utilizarlo. Con las demás formas de cast habrá menos malentendidos entre tú y tu compilador.

Cast entre tipos nativos de C++

static_cast

El mayor uso de static_cast es convertir tipos nativos. Por ejemplo

int a = 5;
unsigned short int b = static_cast(a);
float c = static_cast(a);

Aunque para el programador los dos static_cast parecen lo mismo, el compilador debe hacer dos cosas distintas. A convertir de un tipo entero a otro, le basta jugar con los bits. (Básicamente debe llevar el bit de signo a una nueva posición.) Al convertir entre números enteros y números de coma flotante el compilador debe generar código más complejo, ya que la representación de bits es bastante distinta en tipos flotantes.

static_cast sirve también para convertir clases con el operador de cast como veremos más abajo. Si no sabes con qué cast a probar primero, entonces static_cast es una buena opción.

reinterpret_cast

Como indica el nombre, reinterpret_cast reintepreta un tipo como si fuera otro. Esto funciona especialmente bien entre punteros, ya que tienen el mismo formato físico aunque apunten a tipos diferentes. No obstante, reinterpret_cast reintepreta todo. Convertir un int a un puntero de función te traerá una cuantas advertencias del compilador, pero con reinterpret_cast se puede hacer.

Esta claro que algo tan poderoso invita al abuso. Por eso, la mayoría de las guías de programación te recomiendan con toda corazón de estudiar alternativas. Porque suele haber alternativas como static_cast – a menos que realmente quieres convertir números enteros a punteros de funciones.

const_cast

const_cast sirve para añadir y quitar atributos de tipos. Atributos son const, volatile y mutable. En la mayoría de los casos, const_cast sirve para quitar el atributo const de una variable – de ahí el nombre del cast.

Obviamente quitar el atributo const destruye la buena intención de no modificar una constante que, por lo tanto, las guías de programación recomiendan a no hacer. Sin embargo, suele ser justificado de usarlo para no romper la const-correctness de tu código por tener que usar una función sin const-correctness.

Cast entre clases

dynamic_cast

dynamic_cast se usa cuando sé que una instancia de una clase base es realmente una instancia de una clase derivada. Se usa típicamente en funciones virtuales.

class Base
{
public:
    void hazAlgo(const Base& instancia)
    {
        // Usar una función puramente virtual
        hazAlgoMás(instancia);
    }
private:
    virtual void hazAlgoMás(const Base& instancia) = 0;
};

class Derivada : public Base
{
private:
    virtual void hazAlgoMás(const Base& instancia)
    {
        // Aquí sé que instancia es realmente del
        // tipo Derivada.
        const Derivada& instancia_de_clase_derivada =
            dynamic_cast(instancia);
        // Aquí haz algo con instancia_de_clase_derivada
    }
};

void main()
{
    Derivada uno, dos;
    uno.hazAlgo(dos);
}

En el ejemplo arriba, llamamos para el objeto uno al método hazAlgo. Como la clase derivada no sobrescribe este método, llamamos realmente al método de la clase base. hazAlgo llama a una función puramente virtual hazAlgoMás que se resuelve al método Derivada::hazAlgoMás. La clase Base no sabe nada de la clase Derivada y por eso pasa como parámetro una referencia a un objeto de tipo Base, pero que realmente es un objeto de tipo Derivada. En la implementación del método virtual sabemos que el parámetros instancia debe ser de tipo Derivada y lo obtenemos mediante dynamic_cast.

No nececitamos hacer un cast para el puntero this. El sistema de métodos virtuales ya lo hace por nosotros.

Si el dynamic_cast no succede, por ejemplo, porque el tipo a que se convierte no está dentro del árbol de herencia del objeto a convertir, entonces dynamic_cast lanza un excepción std::bad_cast. Por este sistema de manejo de error, se considera dynamic_cast un método seguro para convertir entre clases del mismo árbol de herencia.

Por cierto, no es necesario convertir una instancia de una clase derivada a una instance de la clase base. Un objeto de una clase derivada siempre es también una instancia de la clase base.

Constructor con un parámetro

Considera un constructor del estilo

class MiClase
{
    explicit MiClase(const OtroTipo& fuente);
};

Este constructor permite inicializar una instancia de MiClase con un objeto de OtroTipo. Pues, esto no es otra cosa que una conversión de tipos.

No hay muchos situaciones en que un constructor con otro tipo es útil, pero cuando es útil, entonces este constructor es la manera recomendada de conversión. Como conversiones implícitas no están recomendadas, hemos escrito la palabra clave explicit delante. Esta nos obliga llamar al constructor explícitamente.

OtroTipo fuente;
MiClase a;
a = fuente; // Error, conversión implícita
a = MiClase(fuente);  // Bien, conversión explícita

Operador de cast

Se puede definir un operador que convierte una instancia de una clase a otro tipo.

class MiClase
{
    explicit operator bool() const;
};

void main()
{
    MiClase c;
    bool b1 = c;  // Error conversión implícita
    bool b1 = (bool) c;  // Bien, pero estilo C
    bool b2 = static_cast(c);  // Bien. Forma recomendada
}

Como vemos, el operator bool permite utilizar la instancia de la clase como si fuera otro tipo. En lugar de bool podríamos poner cualquier otro tipo nativo o clase.

Desde el estándar C++11 podemos escribir explicit delante la declaración para evitar conversiones implícitas. Aunque hemos mencionado arriba que conversiones implícitas no suelen ser deseadas, se usan con abundancia en la biblioteca estándar para las clases traits como std::is_constructible. No obstante, detrás de estas clases suele ser una implementación con templates que deben poder utilizarse como una función. Es decir, un asunto con que no queremos ruinarnos el – hasta ahora – buen entendimiento del tema.

Cambios en los nuevos estándares de C++

Bjarne Stroustrup, el inventor de C++, que ha introducido static_cast para evitar los cast al estilo C, ahora recomienda no utilizar static_cast tampoco. (Ver en los CPP Core Guidelines aquí y aquí.) Es por la misma razón: porque a veces no hace lo que el programador espera. O porque uno se hace la vida demasiado fácil.

De todas formas podemos esperar la introducción de nuevos y más seguros formas de cast como el todavía no estándar narrow_cast. Y ya tienes perlas como std::move y std::forward para resolver problemas que, posiblemente, todavía no has tenido.

En todo caso, si desarrollas código para sistemas que pueden causar daño a personas (que son básicamente aquellas donde las licencias de Microsoft y Apple te prohiben utilizar sus sistemas operativos), entonces vale la pena a echar un ojo al buen uso de los cast y controlarlos con una herramienta de análsis de código estática.

Referencias

Lectura adicional

Si quieren ver los ficheros en tu ordenador en un control policial u otro organismo de seguridad o inseguridad, git te puede ayudar a esconder datos. Crea un repositorio git y crea dos ramas. En la rama master guardas datos no sospechosos y en la otra con nombre no sospechoso guardas los datos que quieres proteger. Entonces, con git checkout master puedes rápidamente esconder lo que no quieras que vean. La carpeta “.git” está escondida y no contiene ficheros con nombres claros.

No olvides que esta forma de esconder datos sólo funciona si el atacante no piensa en este truco y no conoce git. Esta propuesta camufla el hecho que hay datos de interés, pero no los encripta. También ten en cuenta que un cambio de la rama puede durar un tiempo si son muchos ficheros.

Lectura adicional

Cada página debería tener una declaración de privacidad – aunque sólo sea para declarar que el usuario realmente no tiene privacidad. Esta declaración a continuación puedes usar como ejemplo para una página tuya albergada en algún proveedor de tipo aspiradora de datos. Seguramente no prometes demasiado (poco) aquí.

Sus datos privados son importantes. Por eso intento obtener un máximo de ellos en esta página. Como no tengo ni conocimientos técnicos ni derechos de acceso suficientes a la base de datos de esta aplicación web para poder espiar su uso de navegador, subcontraté el tratamiento de sus datos al proveedor de estas páginas.

El proveedor, sus representaciones nacionales y sus partners, llamados simplemente “proveedor” a continuación, usan la declaración de privacidad que puede encontrar en cualquiera de sus páginas. En corto dice que el proveedor puede – o no – tratar sus datos para saber más sobre usted y poder ofrecerle la mejor publicidad y “servicios”. Como el proveedor no tiene capacidad de hacer todo esto solo, puede pasar o no – sus datos a empresas subcontratadas en su país u otro lado que, a su vez, pueden pasar o no – sus datos a empresas subsubcontratadas que posiblemente no están localizadas en su país, su continente o su planeta. Por supuesto, para los partners de su proveedor valen las misma reglas estrictas como para el proveedor.

Además, el proveedor está obligado a dar acceso a sus datos a las “autoridades” si alguna ley en algún país le obliga. Sobre esto no hay más explicaciones en la declaración de privacidad de su proveedor, porque, en EEUU y otros países, está prohibido por ley de admitir la colaboración con las “autoridades”. Las “autoridades” de EEUU y de otros países tienen un derecho general de copiar todos sus datos y pasarlos a base de alguna ley real o deseada a “autoridades” o “instituciones” en otros países amigos o no tan amigos. Estos países se han obligados a proteger sus datos con la misma diligencia. En otros países más allá no es necesario advertir de las posibilidades de espionaje, porque el servicio secreto está omnipresente y sus poblaciones acostumbrada a que toda la comunicación por Internet o teléfono será supervisada por “su seguridad” por los señores en el poder.

Para que el proveedor pueda mostrarle la publicidad que más eficazmente le hace comprar, usted debería usar este y los demás servicios del proveedor al máximo – en particular la búsqueda por Internet para que el proveedor sepa que le mueve y la aplicación de red social para saber lo que le ha movido. Por conveniencia no necesita introducir todos los datos en todas las aplicaciones de nuevo. Gracias a una cookie con su identificación de usuario, que cordialmente se ha ofrecido a su ordenador, el proveedor puede conectar todos sus datos introducidos en cualquier aplicación. Usted sabe que hizo todo bien, cuando encuentra en diversas páginas web publicidad para los medicamentos contra la enfermedad sexual que recientemente ha buscado en la máquina de búsqueda del proveedor.

Está invitado a hacer click en todos los botones de “compartir” que suelen aparecer al final de la página para distribuir sus datos igualmente a todas las empresas y prevenir así un monopolio de datos potencialmente malo para la economía. Por supuesto, sus amigos en las demás redes sociales también están curiosos que usted divulga en Internet. En el caso de sus amigos, usted podría incluso llegar a tener la sensación que es usted quien controla el acceso a sus datos.

Si usted no está conforme con distribuir sus intereses de esta forma, entonces usted debería volver a escribir cartas en papel o comprar los periódicos en un quiosco en lugar de perder su tiempo en Internet.

Lectura adicional

Existen no sé cuántas páginas en Internet que te explican por qué el control de versiones git es mejor que Subversion. No quiero decir que git es peor, pero poco de lo que provee realmente necesitas en muchas ocasiones. Así quiero dar razones por qué quedar con Subversion – sobre todo para los proyectos que ya lo están usando.

Trabajar en Corea del Norte

El cambio más importante de git respecto a Subversion es tener múltiples repositorios en lugar de uno. Mientras en Subversion te puedes confundir, sobre qué rama trabaja tu copia de trabajo, con git te puedes confundir sobre la rama y, además, sobre si has publicados tus cambios locales a qué otro repositorio.

Ninguna página sobre git pierde advertir, que estos repositorios locales te permiten trabajar off-line. Esto habría sido una maravilla con la velocidad del internet en el siglo pasado. Hoy no conozco ninguna empresa informática donde los empleados serían capaces trabajar sin red. ¿Qué te sirve hacer un check-in local si no puedes compilar porque el compilador descarga bibliotecas de un disco en red? Por supuesto, otra cosa es si te envían frecuentemente a Corea del Norte u otro lugar, donde el WIFI de alta velocidad no suele ser la regla.

Copiar repositorios enteros en lugar de ramas

Otra publicidad para git suele ser el cheap branching (ramificación barata), es decir, crear ramas nuevas cuesta poco. Crear ramas es rápido en git porque sólo se crean en tu repositorio local. En Subversion debes crear la rama en el repositorio. No obstante, no ralentiza mucho. La mayor parte de la creación pasas tecleando el nombre de la nueva rama – algo que git no te ahorra tampoco.

Ahora depende como quieres trabajar con tu nueva rama. Una es reciclar tu copia de trabajo y hacerla apuntar a la nueva rama. Esto se hace con switch en Subversion y con checkout en git. Como git no accede a la red, es más rápido. No obstante la pregunta es, si realmente quieres trabajar así, porque puedes confundir la rama en que piensas trabajar con la en que realmente trabajas. Los ficheros modificados en tu copia de trabajo se mantienen y serán ahora ficheros modificaciones sobre la nueva rama a que cambiaste. Si la rama anterior era de una versión de hace un año, con un poco de suerte no sólo harás un check-in de las tres líneas que has modificado sino, además, del estado que tuve el fichero hace un año.

Por no provocar tu suerte decides a editar cada rama en un directorio aparte. Así el flujo de trabajo es así: en Subversion, creas la rama en el repositorio y haces un check-out de lo que necesitas. En git haces una copia (clone) del repositorio entero y creas la rama. Es decir, primero copias el repositorio entero y no sólo lo que necesitas. Pero luego crear la rama, esto, sí, es rápido.

Como no querrás esperar a tanto cheap branching para cada rama que creas, git invita a crear muchos repositorios de que cada uno contiene lo mínimo posible. Puedes crear repositorios pequeños también con Subversion, pero no lo necesitas. Puedes tener un mega-proyecto en un repositorio y no hace falta comprarte un nuevo disco duro para tu copia de trabajo, ya que sólo necesitas descargarte le parte que quieres modificar.

Guardarlo todo

Otra auto-publicidad de git es guardar ficheros enteros en lugar de diferencias. La ventaja: es más rápido al hacer un check-out, ya que git sólo necesita buscar el fichero dentro de un commit y copiarlo. Subversion, en cambio, debe reconstruir el fichero a base del primer check-in más todos los cambios de la historia.

La ventaja de Subversion es que descargas un fichero por la red. En git descargas el repositorio entero con todas las versiones enteras de todos los ficheros – y así lo tendrás también guardado en tu disco duro.

El directorio .git

Git usa una carpeta escondida den nombre “.git” en el directorio raiz para guardar sus datos. En muchas páginas puedes leer que Subversion no es así, porque guarda una carpeta escondida “.svn” en cada carpeta. Esto era cierto. No obstante, esto ha cambiado desde la versión 1.7 de Subversion. Ahora sólo hay una carpeta “.svn” en el directorio raiz – igual como en git.

Seguridad para la generación selfie

Git hace publicidad con la seguridad de los datos. Una revisión no tiene un número, sino un hash, es decir un número hexadecimal lo suficientemente largo, para que, con seguridad, no podrás memorizarlo. Esto es para que no se altere la integridad del commit. El hash se calcula a base de los datos que componen un commit. Cambiar un byte cambia el hash. Al mismo tiempo, git te permite unir varios commits en uno, mover el origin de una rama a otro commit y hacer desaparecer commits por completo. Lo importante es que los commits no aparezcan como si hubieras probado algo, sino que la historia del repositorio muestra que hiciste todos los cambios completos y correctos a la primera. Sólo falta el botón “me gusta”.

En Subversion, en cambio, se queda la historia sin alterar. Lo que está dentro, se queda – a menos si lo quieres hackear. Si haces un merge de una rama, entonces no se copia la historia entera de la rama añadida sino se crear un nuevo commit, donde puedes poner todos los comentarios sensatos que el release manager necesita para decidir si tu cambio entra o no. Los comentarios y check-ins caóticos de tu rama de desarrollo no entran, porque se quedan únicamente en la rama de desarrollo.

Es cierto que tu repositorio de Subversion acumula ramas de desarrollo difuntas que ocupan espacio aunque ya no se necesitan. En git, el repositorio central sólo necesita guardar lo que realmente está terminado y funcionando. Pero esto surge porque estás dispuesto a jugar con dos repositorios a la vez. Lo puedes hacer también en Subversion. Exportas la rama de release, copias los ficheros a la copia de trabajo del repositorio bonito y haces un check-in.

Un repositorio para el back-up

Finalmente queda por hacer una copia de seguridad de tu repositorio, porque no quieres perder tus cambios si se rompe tu disco duro. Normalmente sólo se hace una copia de seguridad del repositorio central. En Subversion, todos los commits entran en la próxima copia de seguridad. En git, te debes pensar si haces un push al repositorio central o creas algún repositorio intermedio (probablemente en red), sobre qué el administrador de sistema hace una copia de seguridad. Así puedes elegir entre olvidar el push al repositorio central o intermedio o olvidar la copia de seguridad de tu repositorios locales.

Se puede argumentar que cualquier directorio de git es un back-up del repositorio central. Esto puede ser válido pero también puede que no. Es posible que sólo empujas commits buenos al repositorio central y el repositorio local contiene muchos más commits “de trabajo”. Es posible que estos no quieres tener en el repositorio central. Además hay configuraciones que son locales a un repositorio determinado – por ejemplo los scripts (hooks) que se llaman en caso de un commit. En fin, es probable que todavía prefieres hacer un back-up del repositorio central como se hace con el repositorio de Subversion, porque la existencias de múltiples clones no te tranquiliza lo suficiente.

El pull request

Una ventaja en el ámbito de git es el pull request. Es un flujo de trabajo en que un desarrollador dice que ha terminado algo y pide al jefe de integrarlo en la rama de release. Antes unas cuantas personas más pueden dar su acuerdo o incluso añadir nuevos commits con cambios. Esta funcionalidad no es de git mismo, sino un software adicional que ofrecen servidores para proyectos en git como GitHub.

Los servidores de Subversion no suelen ofrecer esto aunque teóricamente podrían. Pero es cuestionable si hace falta. En muchos proyectos, los desarrolladores pueden comunicarse de otras formas, se puede convenir que nadie hace un check-in en la rama de release menos el autorizado y siempre es posible crear una rama de la rama para proponer modificaciones adicionales. No aparece todo tan bonito en una página web, pero posible, sí, que es.

Y claro, siempre es posible que alguién implemente algo parecido al pull request para Subversion.

Quédate con lo que tienes

Si ya trabajas en git, entonces déjalo. Git no es peor que Subversion. Pero antes de migrar mil proyectos a git, piénsatelo bien. La mayoría de los argumentos a favor de git se mueven en el nivel de alguien que quiere vender su coche normalito para comprarse un SUV. Como no hay razones de verdad a favor de un SUV, busca razones que, al menos para él, aparentan serlo. El SUV gasta más, cuesta más, ocupa más espacio y es tan estrecho por dentro que la rueda de repuesto no cabe. Pero es mejor porque tiene capabilidad de todo-terreno. Aunque esta nunca aprovecharé porque no quiero que me salpique la tierra a mi bonito coche.

Antes de invertir en git, aprende a diferenciar los usuarios de los creyentes en git. Haz un análisis sobre qué esperas de un control de versiones y piensa si el cambio vale la inversión. Si ni siquiera te convence su propia publicidad, entonces es mejor seguir con lo que tienes.

Los repositorios descentralizados son una ventaja cuando los desarrolladores están distribuidos sobre el planeta como suele ser en muchos proyectos de código abierto. No obstante, en las empresas, los desarrolladores suelen trabajar en la misma oficina. Y existe un buen compromiso: el programa git svn permite trabajar con Subversion como si fuera git y, al mismo tiempo, permite trabajar con Subversion como si fuera Subversion. Es decir, es como un coche normal pero con tracción de cuatro ruedas.

Referencias

  • Página principal de Subversion
  • Página principal de git. git svn es parte del paquete git.
  • Página principal de GitHub

Lectura adicional

Punteros a funciones son variables que guardan una dirección de memoria: la dirección de una función. Punteros son una especie de referencia. En muchos lenguajes de programación existe el concepto de referencias a funciones que se pueden guardar en una variable. En este artículo hablamos de punteros de funciones como se usan en los lenguajes C y C++. Sin embargo, los conceptos se dejan aplicar a cualquier tipo de referencias a funciones – también a aquella otra que usa C++: los functores.

El caso clásico de usar punteros a funciones es una cola de eventos. Si programas una cola de eventos, entonces no sabes cómo se llamarán las funciones (manejadores de eventos) que se registrarán. Por eso no puedes llamarles por nombre. De hecho, colas de eventos se implementan frecuentemente en una biblioteca cuyo desarrollo es independiente de la programación de las funciones a registrar. Pues, no sabrás los nombres de las funciones y tampoco necesitas saberlos.

Tu cola de eventos suministras una función de registro. Esta función tiene un parámetro cuyo tipo es una función. Es decir, aunque no sabes los nombres de las funciones, sí, obligas a que tengan un tipo conocido: el que defines tú.

Tu cola de eventos tiene una lista con punteros a funciones que tienen este tipo. Por este punto de vista, una cola de evento no es muy diferente a una base de datos, donde el usuario puede guardar (es decir, registrar) datos. La differencia es que a los datos de una cola de eventos se puede llamar como función.

La ventaja de todo esto: Puedes llamar a un número arbitrario de funciones sin necesitar la declaración de estas funciones de antemano.

No obstante, esto lleva también una pequeña desventaja. Herramientas de análisis de código pueden extraer del código, qué función llama a qué otra función. Esto es una inforamción útil cuando quieres saber a qué “clientes” afectas al cambiar una función. No obstante, este análisis no funciona con punteros a funciones, ya que el valor de ellos sólo se sabrá a tiempo de ejecución. Sin embargo, esto no suele ser una restricción importante. Las funciones que se llaman mediante puntero suelen ser pocas y bien claras. Por ejemplo, una función de nombre OnMouseClick es fácil de reconocer como manenjador de eventos. Además, no es buen estilo de llamar a manejadores de eventos por nombre desde otras funciones. Así no lo harías, ¿verdad?

Muchas colas de eventos suelen guardar más datos que el puntero a la función. La razón es que no quieres escribir 20 manejadores para eventos que sólo se diferencian por un parámetro. La función de registro te permite añadir un dato de tipo identificador, que pasa como parámetro al manejador de evento. El manejador de evento es, por lo tanto, un tipo de función que lleva este parámetro. Dentro del manejador puedes leer este dato y saber así, a base de qué registro fue llamado. Así puedes registrar la misma función para varios eventos.

La pregunta es: ¿qué es el tipo más adecuado para este dato identificador? Pues, muchas implementaciones usan un tipo “cualquier cosa”. En Java, esto sería un Object, en C y C++ gusta un puntero de tipo void* con un parámetro adicional con el tamaño del buffer a que el puntero apunta. De esta forma puedes registrar cualquier dato en la cola de eventos, sin que la cola necesita conocer su estructura. El tipo real sabrá el manejador de eventos, que convierte el tipo “cualquier cosa” en el tipo que realmente espera.

// Al registrar el manejador usas un identificador de tipo struct Id
struct Id
{
    int index;
    char subindex;
};
Id mi_id = { 5, 1 };
cola_de_eventos.registra(mi_callback, mi_id);

// Tu manejador de eventos haría esto
void mi_callback(const void*const datos)
{
    // Tu manejador sabe que está por detrás de estos "datos"
    const Id*const id = static_cast<const Id*const>(datos);
    
    // Aquí haría algo con estos datos como
    // switch (id.index) ...
}

Ten en cuenta que los datos mencionados arriba se guardan junto con la dirección a la función de antemano. Pues, identifican el registro. No es lo mismo que parámetros que se producen junto con el evento. Por ejemplo, un manejador de OnMouseClick esperaría las coordenadas del ratón, donde tuvo lugar el click. Cómo diferentes eventos pueden producir diferentes datos subyacentes, se encuentra a menudo la idea de usar un tipo “cualquier cosa” también para estos parámetros generados en el evento.

Cuando creas un registro de funciones como una cola de eventos, te deberías preguntar, qué tipo de datos quieres permitir. Un tipo “cualquier cosa” da mucha flexibilidad – también para su abuso. Y no es necessario si sólo quieres permitir un identificador.

Si prefieres usar clases en lugar de punteros, define quién debe hacer la limpieza en la memoria. Pasar un objeto por referencia obliga a guardarlo hasta que ya no se usa. Pasar un objeto por copia deja claro al autor del manejador que se debe hacer una copia antes de terminar el manejador si no quiere perder los datos.

Piensa también que C++ permite functores. Functores son clases que sobrecargan el operador (). Así se puede tratar una instancia de esta clase como función: mi_instancia() llama realmente a mi_instancia.operator(). Functores son instancias de clases y estas se protegen mejor contra un fraude como lo permitirían los punteros. ¡Imagínate que el autor de la cola de eventos es malo y no llama al puntero de función que registraste sino a esta dirección más cuatro bytes!

Como ves, punteros a funciones son muy útiles para determinadas tareas como cola de eventos. Puede tener sentido guardar datos de identificación junto con el puntero y puede ser útil manejar un tipo “cualquier cosa” para pasar datos de tipo arbitrario. No obstante es importante de pensar bien, si es mejor restringir la flexibilidad.

Lectura adicional


Estamos acostumbrados a que nuestros ordenadores están cada vez mas conectados. Esto facilita el trabajo – también para quienes nos espian. Desgraciadamente no es suficiente añadir medidas tecnicas. También debemos cambiar nuestra actitud.

No será como antes

Como analogía nos pensamos en una pequeña aldea remota, donde los pocos visitantes se tratan con hospitalidad. El gobierno decide construir una carretera entre dos ciudades que pasa por esta aldea. Con esta carretera, la aldea esta ahora conectado al gran mundo. Y con ella vienen los criminales. Tras un par de robos, los aldeaños deciden a establecer contramedidas.

Al principio los aldeaños sólo optan por medidas técnicas y administrativas: se emplea policía, se instalan camaras de supervisión y se controlan los carnés de identidad. Es decir, en juerga informática, se instalan un antivirus y un firewall.

No obstante, los aldeaños se dan cuenta que esto no es suficiente. Mientras antes se daba la bienvenida a todos, ahora reina la desconfianza. Aunque el trabajo de las fuerzas de seguridad está bien, no se puede vigilar todo a todas horas. Los aldeaños deben cambiar su comportamiento: deben cerrar las puertas a llave cuando antes entraban en la casa de cualquier vecino sin problema, deben poner vallas donde antes hubo un paisaje abierto y deben estar atentos a personas sospechosas. Cuando antes los aldeaños eran un poco tímidos, ahora deben tomar iniciativa y preguntar a los visitantes sobre sus intenciones.

Como uno puede imaginar, todos estos cambios no son fáciles para los aldeaños. Antes tenían una vida simple y ahora deben pensar en llaves, seguir las vallas y acercar a otra gente. Seguramente se quejarán mucho de esto, pero es la única manera de protegerse contra los malos de fuera. Cuando antes lo acceptan, antes se ajustan a las nuevas necesidades.

El Internet tampoco es como antes

Algo parecido a los aldeaños ha pasado en Internet. En los años noventa del siglo viente eramos felices, cuando una página web se descargó en un tiempo razonable. Hoy la tenemos en pocos segundos e incluso hay tiempo para bajar JavaScripts adicionales que envían nuestros datos a unos cuantos servidores más. Podemos hacer vídeochats sobre servidores que son lo suficientemente potentes para hacer una copia de nuestro vídeo y pasarla a varios servicios secretos en tiempo real.

Ten en cuenta que el peligro por el robo de datos normalmente ni es espectacular ni obvio ni inmediato: un puesto de trabajo que ya está ocupado, un control policial así de repente, un seguro de salud de que un día te enteras que es mucho más caro que el de tu amigo. No sabes, quién tiene cuántos de tus datos y cómo lo usa. Y, sí, ¡tienes algo que esconder! Pero eso no andas desnudo por la calle, ¿verdad?

Debemos aceptar, que todo lo que enviamos por Internet, está a disposición de todos por todos los tiempos aunque nosotros lo hemos subido como “privado”. Podemos enfadarnos que tantas organizaciones roban nuestros datos, pero ellas no se cambiarán.Será más fácil cambiar nuestra actitud. (Recuerda el tiempo que la gente ya intenta cambiar la iglesia católica.)

Cambio de cultura

Debemos acostumbrarnos, que no todo lo que es posible es recomendable. Debemos aceptar a utilizar una manera más difícil para nosotros si esta pone la vida mucho más difícil a posibles atacantes. Incluso debemos estar dispuestos a no utilizar ciertos servicios si no son seguros. Esto puede llegar a costarnos oportunidades y amistades. Y seguramente nos cuesta potencia neuronal y tiempo.

La nueva cultura en un ejemplo

Quiero dar un ejemplo, qué este cambio de cultura puede significar concretamente.

El coste real: datos o dinero

Queremos mantener una copia de seguridad de nuestros datos. La manera fácil es subir todo a la nube. Ahí hay alguien quien se encarga a proveer los discos necesarios para guardar todo y hacer las copias suficientes para que se pierda ningún dato si uno de estos discos falla. Probablemente este alguien no lo hará realmente gratis. Simplemente no le pagas en dinero sino en datos. Y con estos datos sacarán aún más dinero contigo más tarde.

Ahora lo haces diferente. Te compras un disco duro para tu copia de seguridad y otro disco para tener una segunda copia. El disco pagas tú, pero nadie más tiene el disco que tú. Es decir, un cambio de cultura es estar dispuesto a pagar por lo que usas. Bueno, quizá no es un cambio sino simplemente aplicar la cultura que ya tienes en otros dominios.

No todo por la red

Lo más comfortable es montar tu nuevo disco duro en red. Así está disponible para hacer copias de seguridad en cualquier momento. El disco conectas al mismo router que usas para navegar por Internet. Pues, probablemente tu router no está infiltrado.

Más seguro aún es si no conectas tu disco duro por la red. Lo conectas con tu ordenador cuando hace falta. Si no lo usas, lo desconectas. Así disminuyes el tiempo que un atacante tiene para hacerse con su contenido. Aunque este tenga acceso completo a tu máquina, no tendrá tiempo suficiente para enviarse todos los ficheros. Para ti, conectar y disconectar el disco es un cambio pequeño; para el atacante requiere encontrar el momento en que puede empezar y renanudar su misión.

Más seguro aún es utilizar un ordenador (viejo) sin conexión de red alguna. Aunque tú apagas la antena, un virus bien programado lo puede dejar encendido sin que te des cuenta. Por eso se venden ordenadores que simplemente no tienen antena: los portátiles air gap. No obstante, antes de gastar dinero considera utilizar una máquina vieja.

Utiliza encriptación

Cómo no querrás renunciar a la red por completo, al menos encripta todo lo que puedas. La forma más potente es que tú encriptas tus datos en tu máquina antes de subirlos. Entonces los señores en medio pueden copiar lo que quieran – sólo ven datos encriptados.

No olvides que una conexión https sólo encripta el canal entre tú y la página web que usas. El proveedor de la página puede leer todo. Si no quieres esto, debes encriptar tus datos antes de enviarlos.

En general evita enviar más de lo necesario: aunque la encriptación hace imposible descifrar tus datos ahora, el avance tecnológico puede hacerlo posible cuando te quieres presentar a las elecciones presideciales.

Una lista de consejos

Quiero enumerar algunos cambios de comportamiento simples que deberías considerar aparte de las medidas técnicas. Estas medidas no harán un robo de datos imposible. Pero, sí, suben bastante el coste para robarlos.

  • No generes más datos que necesario. No hagas fotos o bórralas si no quieres guardarlas. No escribas cosas en los foros públicos que no quieras que salgan en un periódico. Borra datos que ya no tienen uso. Si no quieres destruir el disco, bórralos seguro (sobrescribiéndolos varias veces).
  • Evita subir más datos que necesario. No reenvíes todo que te envían, no te registres en todo, evita poner tu nombre en claro si te obligan registrar. Ten en cuenta también los meta-datos: cuado te has conectado a qué y con quién.
  • Separa datos. No pongas toda tu vida en Internet y no todo al mismo sitio. Saber que fuiste 3 veces al doctor no es muy interesante. Saber que compraste un libro sobre testamentos tampoco. Pero saber las dos cosas juntas, ¡sí!. El peligro no viene por un solo dato sino por la conexión entre muchos.
  • Guarda los datos lo más local posible. No subas datos a la nube que no tienen que estar ahí. Para protegerte contra un incendio, guarda copias de seguridad en varios discos distribuidos por la vivienda o en casa de un familiar.
  • Encripta datos. Crea archivos con 7-zip or volúmenes encriptados con VeraCrypt. Encripta también tus copias locales. Si roban tu disco, no podrán leerlo.
  • Acostúmbrate a contraseñas no triviales. Tienes una para entrar en tu sitema, otro para abrir el volumen de ficheros encriptado, otro para entrar en la red y otro más para cualquier sitio de Internet. (En este punto te podría interesar el artículo Cómo memorizar muchas contraseñas diferentes.) Si estás acostumbrado a muchas contraseñas te cuesta dos minutes más a arrancar tu máquina. Al atacante, en cambio, frena mucho más porque tiene muchas puertas cerradas a llave delante.
  • Aprende teclear las contraseñas con un poco de camuflaje – cubriendo con una mano la otra y utilzando diferentes dedos. También es difícil ver lo que tecleas si tecleas rápido con diez dedos sin mover las manos.
  • Verifica checksums y signaturas. Muchos proveedores ofrecen hashs para autentificar su software. Debes aplicar el mismo algoritmo que ellos para obtener el mismo hash – sino hay posibilidad de fraude. Esta verificación es un tema avanzado y no deberías preocuparte si no lo entiendes – y todavía ni sigues los otros consejos.
  • Usa máquinas virtuales para cada tipo de navegación: Una para el banco, otro para tu email, y uno más para navegar por donde sea. Para un virus es mucho más difícil contaminar todas las máquinas virtuales que sólo una real. Si un virus distruye tu máquina virtual, la borras y creas una nueva.
  • Trabaja offline. Una máquina virtual puede ser sin conexión de red. O mantienes un viejo ordenador sin conectarle a la red. Debes copiar ficheros a mano ahí, pero el atacante también. Y este no vive al lado probablemente.
  • Apaga la conexión de red si no la necesitas. En los ordenadores portátiles puedes apagar la conexión de red con una combinación de teclas como Fn + F2. Esto no te protege contra un virus en el firmware, pero acorta el tiempo que tiene un virus de software a enviar los contenidos de tu pantalla como película en tiempo real.
  • Restringe el acceso físico al hardware. Es posible añadir algo más que envía datos sin que te enteras. Esto vale sobre todo para routers en empresas.
  • No te conectes a todo en todos los sitios. No te conectes a una página que requiere contraseña, si sólo hay un Wifi no encriptado. Mejor lee el periódico en papel.
  • Piensa en papel. Es más trabajo interceptar una carta que un email. Papel mantiene la información más tiempo que cualquier memoria electrónica y se puede destruir perfectamente en un fuego.

A pesar de todo cambio de cultura, no olvides que también se requieren medidas técnicas.

Referencias

  • El programa de código abierto 7-zip permite tratar muchos formatos de archivos e crear archivos encriptados. Se puede instalar una extensión al Explorador de ficheros en Microsoft Windows. En Linux se debe instalar el paquete “p7zip-full”. El paquete “p7zip” solo no basta.

    Al contarario del viejo format zip, el formato 7z permite encriptar también los nombres de ficheros que el archivo contiene. No obstante, es mucho mejor un viejo archivo zip encriptado que ninguna encriptación. Si no tienes una caja fuerte como los grandes bancos, entonces es todavía mejor dejar tu dinero tras una puerta de madera cerrada a llave que abierto en la mesa. Pues, encriptación con zip es usar la puerta de madera, el formato 7z es la caja fuerte del banco.

  • VeraCrypt es un succesor digno del difunto TrueCrypt. Sirve para crear contenedores de ficheros encriptados y montarlos como unidades de disco.

Lectura adicional

Escribe tu dirección de correo electrónico para suscribirte a este blog, y recibir notificaciones de nuevos mensajes por correo.

Únete a otros 52 seguidores

Archivos

diciembre 2018
L M X J V S D
« Nov    
 12
3456789
10111213141516
17181920212223
24252627282930
31