You are currently browsing the category archive for the ‘Código bonito’ category.

Métodos y funciones hacen algo. Por esta razón muchas guías de estilo exigen que sus identificadores deberían comenzar con un verbo.

Una exigencia adicional es que conviene nombrar las mismas acciones con los mismos nombres. Y la mejor forma de no olvidar es apuntárselos. Aquí presento un pequeño diccionario de verbos que puede servir como ejemplo para un diccionario corporativo.

Programar en español presenta una dificultad añadida por las flexiones del verbo, ya que se pueden usar el infinitivo o el imperativo. Lógicamente no conviene mezclar ambas formas. Cuál forma se usa es una cuestión de gusto. Usando el infinitivo tiene la ventaja que no hay plural y singular sino nada más una forma. No obstante el imperativo se ajusta más a la comprehensión humana de entender un texto, ya que uno conjuga los verbos cuando piensa en español.

Por lo tanto creo que la mejor manera no es traducir las palabras inglesas tal cual, sino usar los verbos juntos con un objeto (un objeto gramatical) para dejar más claro la conjugación empleada – que suele ser el imperativo de la segunda persona singular. Por ejemplo, en lugar de traducir «clear» con «limpia», puede ser más comprensible emplear «limpia_memoria». Más educado, desde luego, sería «limpie_memoria» o «limpie_memoria_por_favor», pero tratar de usted a una máquina exige más respeto que la mayoría de los ordenadores consigue merecer. Por lo tanto, la mayoría de los programadores prefiere moverse lingüísticamente al nivel del Rey de España cuando quiere hacer callar a un presidente bolivariano, que realmente no es de Bolivia sino de Venezuela.

Para más claridad, el diccionario de verbos tiene una columna para las palabras inglesas y presenta una traducción relativamente libre del inglés al español. Palabras o partes de palabras en paréntesis son opcionales.

Dictionario de verbos
Inglés Español Uso
Apply Aplicar(Cambios) Usa los datos ya guardados en la memoria RAM para calcular los datos de salida.
Assign Asignar(Valor) «Convertir» + «Establecer»
Clear Borrar(Elementos) Limpia los contenidos. Borrar todos los elementos de un contenedor.
Close Cerrar(Flujo) Cerrar un flujo o fichero. «Cerrar» tiene semejanza con «LimpiarObjeto» en el contexto de flujos y ficheros.
Config(ure) Config(urar) Usa datos de configuración (estática) para calcular datos dependientes. Esto es similar a «Aplicar» con la conotación de sólo hacerlo una vez durante la inicialización del programa.
Construct Construir Inicializa datos de una nueva instancia. Esto es un sinónimo para «Inicializar» pero en un contexto de objetos dinámicos cuyo ciclo de vida incluye «Crear», «Construye», «Limpiar», «Destruir».
Convert Convertir Convierte datos a un tipo diferente, por ejemplo, un número a una cadena de texto.
Create Crear(Instancia) Reserva memoria para una nueva instancia. Usa «Construir» para separar inicialización y reservación de la memoria. El método «Crear» suele ser un método (estático) de una clase y no uno de objeto, ya que sirve justamente para crear este objeto.
Display Mostrar Lo mismo que «Aplicar» pero con una conotación visual. Aplicar el estado de la memoria a la presentación en pantalla.
Destroy Destruir El contrario de «Crear». Libera memoria de un objeto dinámico. Usa «Limpiar» para separar la eliminación de datos de la desaparación del objeto.
Destruct Limpiar(Objeto) Libera recursos reservados por un objeto dinámico, pero no libera la memoria reservado por este objeto, ya que esto haría el método «Destruir» si se trabaja en un contexto de «Crear», «Construye», «Limpiar», «Destruir». Cuidado con una posible trampa lingüística: las palabras inglesas Destroy y Destruct se traducen ambas a «Destruir».
(Get) (Obtener) Lee un dato de la memoria RAM. Se puede omitir este verbo si es posible llamar el método como el atributo correspondiente.
Init(ialize) Inicializar Asignar un valor por defecto a la memoria. Un sinónimo para «Construir» o «Configurar».
Load Cargar Cargar un fichero entero en memoria. Abrir + Leer + Cerrar.
Open Abrir(Flujo) Abrir un flujo o fichero. «Abrir» tiene semajanza de «Crear» + «Construir» en el contexto de flujos.
Read Leer(Flujo) Leer desde un flujo o fichero.
Refresh Refrescar Un «Actualizar» gráfico. «Establecer» + «Mostrar»
Reset Reset (Reinicializar) «Limpiar» + «Inicializar»
Save Guardar Guardar un fichero entero. «Abrir» + «Escribir» + «Cerrar»
Set Establecer Guarda datos en la memoria RAM
Update Actualizar «Establecer» + «Aplicar»
Write Escribir Escribe datos a un flujo o fichero.

 

Lectura adicional

En este artículo quiero tratar como se puede usar la sentencia return para asegurar succesivamente la calidad de datos y obtener un código más limpio.

El problema

La programación estructurada se inventó para evitar el comando goto. La sentencia return también es una especie de goto ya que salta fuera de la subrutina aunque todavía queda código detrás a ejecutar. Por eso hay guías de estilo que piden que una función sólo puede tener una sentencia de return al final – normalmente para devolver el resultado de la función.

Esto es una buena regla en un cálculo lineal, es decir, cuando no se necesitan comprobar errores antes de seguir. Cálculos matemáticos son muchas veces de este tipo. La suma de a y b no falla aunque las variables contengan datos inválidos.

No obstante, en la mayoría de los casos necesitamos la comprobación de errores. En un escenario real el tratamiento de errores ocupa a menudo más espacio que el cálculo principal.

Veamos un ejemplo. Queremos leer algunos bytes de un fichero. Un programa simple tendría pocas líneas.

char buffer[1000];                       // Un buffer
FILE* f = fopen("mi_fichero.txt", "r");  // Abre fichero
fread(buffer, 1, sizeof(buffer), f);     // Lee algo
fclose(f);                               // Cierra fichero

Si tenemos en cuenta los posibles errores, la misma funcionalidad se extendería así:

char buffer[1000];   // Un buffer
bool error = false;  // Un marcador de error

// Abre fichero
FILE* f = fopen("mi_fichero.txt", "r");  

// Si podía abrir el fichero
if (f != NULL)  
{
	// Entonces lee del fichero
    const size_t read_result = 
		fread(buffer, 1, sizeof(buffer), f);
		
	// Si podía leer el fichero
    if (read_result == sizeof(buffer) 
	{
		// Haz algo
	}
	else
    {
		// Marca error si no podía leer del fichero
        error = true; 
    }
	
	// En todo caso cierra el fichero
    fclose(f);
}
else
{
	// Marca error si no podía abrir el fichero
    error = true;
}

Para cada comprobación que necesitamos para seguir adelante debemos añadir un if anidado. Esto ensucia tanto el código que podemos perder la vista sobre la esencia de la función. La idea principal de abrir, leer y cerrar un fichero queda obfuscada por tanta comprobación de error. Y este ejemplo es todavía bastante simple en comparación con un código real.

Usar múltiples return

Si permitimos el uso de varios return en medio, entonces podemos reducir el número de if anidados.

char buffer[1000];   // Un buffer
bool error = false;  // Un marcador de error

// Abre el fichero
FILE* f = fopen("mi_fichero.txt", "r");
if (f == NULL)
{
    return;
}

// Lee el fichero
const size_t read_result = 
	fread(buffer, 1, sizeof(buffer), f);
if (read_result != sizeof(buffer) 
{
    error = true;
}

// Cierra el fichero
fclose(f);

La filosofía de este procedimiento es consultar en cada paso, si los datos obtenidos son buenos y me permiten continuar. Si no puedo continuar, entonces termina la ejecución. Por ejemplo, si no encuentro el fichero en fopen, entonces no hay nada que leer y debo cancelar la función. Ya no tengo el problema de if anidados y puedo fácilmente añadir una condición más si hace falta.

Los chequeos con return en medio ayudan también a depurar el código. Si la ejecución del programa ha llegado a un cierto punto dentro de una función, entonces ya sé que todos los datos que se hayan obtenido anteriormente fueron buenos. La estructura del código me permite concentrar en cada paso del algoritmo sin dejar de hacer un chequeo exhaustivo de posibles errores.

Un return en medio de la función devuelve típicamente un código de error. Un return que devuevle un código de error notifica una excepción, es decir, un error no deseado. Por lo tanto suele ser posible lanzar una excepción en lugar de devolver un código de error. Cual de las dos formas es preferible es una decisión del diseño del programa a alto nivel y no a nivel de función. Hay lenguajes como C en que no se pueden lanzar excepciones. En otros lenguajes como Java el uso de excepciones es lo habitual.

Por cierto, para salir o saltar en bucles suele existir una sentencia equivalente al return. En los lenguaje C y Java se llaman break y continue, respectivamente. Son menos usado que el return y probablemente con razón. El enunciado return sale de la función sin más buscar. En cambio, las interrupciones de bucles saltan al inicio o al final del bucle que a veces cuesta encontrar – sobre todo en funciones largas y con bucles anidados.

Una solución curiosa

En este apartado presento una idea un poco peculiar. No recomiendo usarla y la presento sólo como una curiosidad.

La idea consiste en encadenar todas las comprobaciones en un sólo if y hacer de paso todo el trabajo.

char buffer[1000];
bool error = true;
FILE* f;
if (f = fopen("mi_fichero.txt", "r") &&
    sizeof(buffer) == fread(buffer, 1, sizeof(buffer), f))
{
    error = false;
}
if (f)
{
    fclose(f);
}

Esta construcción funciona así: se ejecuta fopen y se asigna el puntero que devuelve a f. El valor de una asignación es el valor asignado, es decir el puntero devuelto. Si este puntero vale NULL, entonces se interpreta como un false booleano. En este caso ya no se evalúa el segundo operador de la expresión && y el programa no entra en el if. En el caso que el puntero f no es nulo, se ejecuta fread y se compara con el valor esperado. Si no lo es, entonces no se entra en el if.

La condición del if sólo es cierta si todos los operadores de la expresión devuelven valores no nulos. Al primer valor nulo, se termina la evaluación de la expresión booleana y no se ejecutan las funciones siguientes. Esta forma compacta bastante el código sin dejar de comprobar errores.

Este ejemplo demuestra que alguien entiende mucho de las caracterísicas del lenguaje de programación pero quizá no tanto de la semántica del código. Muchos lenguajes como C y Java permiten asignaciones en expresiones booleanas aunque es mala práctica, ya que una expresión booleana, por semántica, es un valor y no un contenedor de datos. En cambio, este ejemplo no sólo asigna el puntero de fichero sino pone todo el código ejecutable en la condición del if.

En lenguaje humano, un uso semánticamente correcto del if sería algo como «Si lo puede hacer(condición), entonces lo hago (bloque condicional)». El ejemplo arriba es más bien algo como «si lo hago todo, entonces bien».

Conclusión

Hemos visto que la comprobación de errores puede afectar bastante a la claridad del código sobre el fin principal de una función. Dejando aparte la filosofía de una programación estructurada estricta podemos conseguir a reducir el nivel de anidamiento y separar el código en los pasos principales del algoritmo.

Referencias

El ejemplo de la «solución curiosa» fue inspirado en el código fuente de una implementación del algoritmo BSDIFF. Ver, por ejemplo, la función main del fichero bsdiff.cpp, líneas 260 a 264. Para descargar el archivo zip, pincha aquí.

Lectura adicional

Otras palabras claves
Técnicas el programación
Código más bonito

Introducción

Con funcioncitas me refiero a funciones de una o dos líneas, muchas veces con un cálculo muy simple, pero con un valor semántico muy potente.

Escribir una función adicional fracasa a menudo en una cierta pereza, porque hay que buscar un hueco en el fichero fuente, escribirla, en C y C++ hay que añadirlo en la cabecera, eventualmente serás capaz de escribir algún comentario de qué esta función debe hacer y todo esto para una línea de código bastante trivial. Pero esto se puede ver también de otra forma: escribes el código una vez, lo modificas diez veces y lo lees cien veces. Si tardas 99 segundos (1 minuto y 39 segundos) para escribir una función que te ahorra un segundo en la lectura, todavía ahorras tiempo.

El valor de las funciones pequeñas está en su claridad semántica, que mejora mucho la legibilidad del código – sobre todo cuando se trata de una combinación de varias funciones pequeñas. Para ilustrar esto, presento algunos ejemplos.

Copiar con punteros

Si p y q son dos punteros, entonces ¿qué hace el código de C siguiente?

while (*p++ = *q++) {}

Lo que hace es copiar el valor a que q apunta a la memoria referenciada por p. A continuación pasa ambos punteros al próximo y elemento y sigue copiando hasta que haya copiado un cero. (El valor de una asignación es el valor asignado, el cero se interpreta como un false y el bucle while termina.)

Tenemos una expresión booleana basada en cualquier tipo arbitrario que el compilador sepa amablemente interpretar como false o true y que, además, modifica las variables dentro de la expresión. A parte de tener un buen ejemplo de un uso sofisticado pero poco intuitivo del lenguaje de programación C, ¿para qué se podría utilizar un bucle así?

Pues, para copiar un string. En la biblioteca estándar de C hay una definición de strcpy similar a esta (la versión estándar devuelve un char*):

void strcpy(char *p, const char *q)
{
    while (*p++ = *q++) {}
}

Ahora la pregunta: ¿qué te parece más comprensible? ¿Una línea strcpy(p, q) o un bucle while (*p++ = *q++) {}? Técnicamente no trae ninguna ventaja llamar a la función strcpy, pero semánticamente, es decir, el significado del código es mucho más claro para el programador.

Funciones pequeñas son a menudo parte del estándar. La clase String en C# tiene un método IsNullOrEmpty. La expresión

if (!String.IsNullOrEmpty(miString))

reemplaza a

if (miString != null && miString.Length > 0)

Es lo mismo, pero el método IsNullOrEmpty deja la intención más obvia.

Acotar rangos

Un ejemplo simple. Mira el reloj cuánto tardas en contestar a la pregunta. ¿Qué representa c?

c = a < b ? a : b;

Y ahora los mismo otra vez. ¿Qué representa c?

c = min(a, b);

Esto era fácil, pero ¿qué tal con algunos niveles de anidamiento?

c = c < a ? a : (c > b ? b : c);

Esto limita c al rango [a, b]. Si no estás tan firme en el uso del operador ? : podrías escribir la línea anterior con if.

if (c < a)
{
    c = a;
}
else
{
    if (c > b)
    {
	    c = b;
	}
}

Un poco más amigable sería usar las funciones pequeñas min y max:

c = max(a, min(b, c));

Mi opción preferida sería una función

c = limitToRange(a, c, b);

donde el parámetro en medio será acotado al intervalo [a, b].

Puedes optar por hacer una pequeña función que ocupa una línea o la construción con el if anidado arriba que ocupa once líneas por cada variable que quieres acotar. Y puede haber muchas: si c es un índice de un array, entonces podrías tener la necesidad de acotarlo cada vez que accedes al array.

Algo parecido vale si quieres generar un error al recibir un índice fuera de rango. La manera directa sería escribir algo así

if (index < start || index >= end)
{
    std:ostringstream mensaje;
	mensaje << "Error de índice index = "
	        << index
			<< ". Fuera del rango ("
			<< start
			<< ", "
			<< end
			<< ").";
    throw std::exception(mensaje.str());
}

Pero igual vale la pena hasta escribir una clase de excepción propia.

if (indexOutOfRange(start, index, end))
{
    throw IndexException(start, index, end);
}

Conclusión

Funciones pequeñas aumentan considerablemente la legibilidad del código. Ayudan a reducir el número de líneas dedicadas a comprobar la calidad de los datos y dejan a consecuencia más espacio a la parte principal de una función. Para no perder velocidad en la ejecución, C++ ofrece definir estas pequeñas funciones inline.

Muchas veces las funciones pequeñas son tan generales que se pueden usar en todos los proyectos de programación. Una buena gestión debería procurar que se mantiene una biblioteca que todo el mundo en la empresa pueda usar. Como son funciones muy básicas, conviene también pensarse bien sus nombres y el espacio de nombres en que se encuentran.

Si no eres gerente sino un simple programador, entonces te recomiendo que guardes estas funciones en un fichero personal. Así puedes copiar y pegarlas en cualquier proyecto en que participas. De hecho, mantener una biblioteca personal puede facilitar tu trabajo.

Referencias

Introducción

A la primera vista uno diría que no es posible programar orientado a objetos con el lenguaje de programación C porque no tiene clases. Esto es parcialmente correcto, ya que clases son un componente importante para programar orientado a objetos. El lenguaje C, en cambio, fue diseñado para la programación estructurada. Sin embargo, el lenguaje C permite definir objetos.

Programar orientado a objetos es un paradigma, es decir, una forma de diseñar código. Hay lenguajes como C++ o Java que están diseñados para este paradigma, pero no se programa orientado a objetos meramente por utilizar a uno de estos lenguajes. Por ejemplo, si todos los miembros de una clase son públicos y estáticos, entonces equivalen a variables y funciones globales con el prefijo «NombreDeClase::». (Prefijos pueden reemplazar espacio de nombres.)

En cambio, se puede aplicar el paradigma de orientado a objetos a lenguajes de programación no diseñados para esto, aunque será lógicamente con complicaciones. Como resolver estas complicaciones para el lenguaje de programación C es el tema de este artículo.

Definición de clases

Los datos de una clase se pueden guardar en una estructura. De hecho, en C++ un struct no es otra cosa que una clase con todos los miembros públicos. Como C no permite métodos, usamos funciones globales. El equivalente de C++

class MiClase
{
public:
	int miDato;
	void hazAlgo(void)
	{
	    miDato = 5;
	}
};

en C sería

struct MiClase
{
	int miDato;
}

void hazAlgo(MiClase *const miInstancia)
{
    miInstancia->miDato = 5;
}

Nótese que el primer parámetro de la función en C tiene el mismo tipo que el puntero this en C++.

El uso de estas clases sería en C++

MiClase miInstancia;
miInstancia->hazAlgo();

y en C

MiClase miInstancia;
MiClase_hazAlgo(&miInstancia);

Mediante punteros a funciones y macros es posible que se puede construir el aspecto de llamadas a métodos de C++ como en

miInstancia->miMetodo();

Sin embargo, no lo vamos a discutir en este artículo. Se trata meramente de un aspecto estético que, en mi opinión, más confunde que aclara. El lenguaje C no es C++ y programación orientado a objetos no deja de serlo sólo por ordenar de otra forma los componentes de una llamada a una función.

Estructuras opacas

Es posible declarar y utilizar un tipo de puntero a una estructura sin definir la estructura misma. Con este mecanismo se pueden crear estructuras opacas.

En el fichero de cabecera (el .h) defino un tipo de puntero a una estructura

typedef struct MiStruct* MiClase;

La definición de la estructura estará dentro de un fichero de implementación (*.c).

struct MiStruct
{
    int miDato;
};

El interfaz en el fichero de cabecera define un puntero, que es un tipo de un tamaño conocido. Por eso el compilador puede compilar un programa que usa un puntero aunque el tamaño del elemento a que apunta es desconocido. El usuario de MiClase accede a la biblioteca meramente con el puntero.

Este encapsulamiento de datos es incluso mejor que el de C++ ya se define la estructura de datos en el fichero de definición. Este fichero no tiene por qué ser visible para el usuario de la estructura opaca, ya que se puede meramente publicar los ficheros binarios correspondientes. En otras palabras, se puede esconder por completo la estructura de datos. Esto no es posible con una clase en C++, ya que la declaración de la clase en el fichero de cabecera debe contenedor todos los campos de datos.

Herencia

No existe un mecanismo de clases derivadas en C, sin embargo esto se puede simular. El método de una clase/estructura, que hereda de otra, puede convertir el puntero de su estructura a una estructura de la clase base y llamar con ello los métodos de esta. Esto corresponde a una herencia privada. Para esto hace falta que la primera parte de la estructura derivada contiene en el inicio los mismos campos como la estructura base.

Para hacer una herencia pública, el usuario debe convertir el puntero en su llamada.

MiClaseDerivada miInstancia;
MiClaseBase_HazAlgo((MiClaseBase) miInstancia);

Obviamente esta construcción es aún menos práctica ya que es el usuario debe pensar si llama a una función de la clase base o de la clase derivada.

La herencia complica más en el lenguaje C que ayuda. Por esto vale la pena tener en cuenta una alternativa: usar una clase en lugar de heredar de ella.

Identificadores en lugar de punteros

Una manera potencialmente más potente para la programación orientado a objetos es abstraer de los punteros y referenciar objetos nada más que por un identificador numérico. Cada método de una clase debe primero comprobar si el identificador numérico corresponde a su clase correspondiente. Esto es más complejo pero una buena alternativa cuando se quieren manejar muchas clases diferentes en C. De hecho, muchos lenguajes modernos como Java, C# o PHP devuelven identificadores y no punteros a los objetos, lo cual les permite reordenador los objetos en la memoria si hace falta. Sólo el gestor de objetos sabe bajo qué dirección de memoria se encuentra qué objeto.

Un camino intermedio y buen compromiso es exportar un tipo de identificador diferente para cada clase. Entonces la clase gestiona nada más que sus propias instancias.

Resumen

Hemos visto que se pueden crear «clases» y «objetos» con el lenguaje de programación C. Referenciar objetos por punteros a estructuras opacas o mediante identificadores es una manera potente para el encapsular datos.

Por no ser un lenguaje diseñado para la programación orientado a objetos, el uso de este parádigma a veces no queda muy elegante en el código y tampoco es posible aprovechar la potencia de la herencia sin trucos adicionales.

No obstante, la programación orientado a objetos es posible con el lenguaje C y debería tenerse en cuenta a la hora de realizar proyectos grandes. De hecho hay unas bibliotecas que se han creado de esta forma como el servidor X o la biblioteca GObject.

Referencias

Honestamente, el título confunde: Este artículo comenta mucho los comentarios.

Programadores están obligados a hacer su código funcionar. Una restricción que les quita mucha creatividad. Sólo una minoría privilegiada, que escribe comentarios en el código, tiene la posibilidad de expresarse con libertad.

Por qué comentarios

Miles de programadores (bueno, quizá más bien unos veinte) no tienen mayor preocupación que su aparición como coautor en este artículo. Por este motivo evitan el uso de comentarios en su código. Mientas tanto disfrutan el resultado simple y exacto de cada función, que devuelve 3 cuando el parámetro var1 es “OK” y var2 guarda 15. Tres significa que la máquina va a arrancar, el robot gira a la izquierda, el programa va a terminar o el contrario de esto – o todo a la vez o una combinación arbitraria de esto. Depende del contexto, el cual puedes encontrar 150 líneas más arriba o en otro fichero del programa. Mala suerte, si la función está mal y debería devolver 4, que es algo diferente que 3 o lo mismo. Depende del contexto. Hay muchas maneras de escribir código inmantenible.

No te entiendo

Usando comentarios trae la cuestión de lo que es un buen comentario. En general, yo consideraría un bueno comentario alto que explica la semántica y no la sintaxis. Alguien que sabe programar no necesita un comentario como

// Incrementa x en uno
x = x + 1

Sin embargo, es un comentario obligatorio cuando sería el único en una función de 1500 líneas.

Explicar la semántica puede ser difícil a veces. A continuación sigue una lista con ejemplos de la vida real, que muestran lo que no es probablemente un buen comentario.

bool ProcesaTeclaUsuario(void)
// de momento es global por problemas con los punteros

En este programa no hubo ninguna función no-global. Pero, por supuesto, puede haber una razón de no ser global diferente para cada función.

num_mens_control++;  //TODO tiene sentido???

¿Tiene sentido lo que haces ahí? Mientras funciona, todo tiene sentido. En este contexto me es grato presentar la definición

Un científico sabe por qué no funciona. Un ingeniero no sabe, por qué funciona.

Mientras cambias código y no estás seguro si el cambio fue bueno, puedes dejar las líneas originales comentadas y marcar la modificación con una palabra especial. Si tienes mucho que cambiar, entonces un poco de variación puede resultar más gratificante. Estos ejemplos son de un fichero con unos 2000 líneas:

//TODO
//XXX TODO
//WORKAROUND
//WORKAROUND TO DO IT BETTER
//XXX BAUSTELLE
//XXX BAU 28.06

Baustelle es alemán para “obras”. El aviso “constantemente en obras” en una página web es igualmente significativo. Quién esperaría que una página web cambiase.

!! rules necesarias para el correcto funcionamiento:
rule void Nr_Set_Visible_Displayed(object Ob_Strip input)

A diferenciar de las rules no necesarias y las rules para el funcionamiento incorrecto.

!!  start of Md_GbMDW_FPL needed rules

¿Escribes código que no es needed?

!! END OF RULE EXPERIMENTAL

Vale. Lo haces.

Cuando la comunicación falla, todavía te queda decir a otros lo que deben hacer por código fuente.

!! Note: move this(Ob_GbManCoord) to strip model.
export variable object Ob_GbManCoord;

Por supuesto, lo podrías hacer tú mismo, pero ¿por qué?

!! New model for waypoints line
model MdGb Md_Gb_Waypoints

O este comentario es actualizado frecuentemente o este modelo será nuevo para siempre.

!! Study assigment of colours
if Ob_Fp_Strip.In_Flag_Un_Flag_Field then

A veces, un ordenador toma un respiro se pregunta: ¿En qué color debería pintarlo?

!! Rule to solve the STR-1207: Panning in Playback mode

Resuelto. Una vez y por todas. Ni piensas en que este código podría ser útil para otro propósito.

//declarar el objeto, es importante inicializarlo a NULL
TStringList *Valores = NULL;
Valores = new TStringList; //crear instancia del objeto

Esto es un comentario importante. De otra forma el lector podría llegar a pensar que no vale la pena asignar NULL a un puntero que será sobrescrito inmediatamente después.

Como comentar mejor

En general podrías tener en cuenta las siguientes ideas para escribir un código.

  • Como un programador ya sabe el lenguaje de programación, no necesitas explicárselo de nuevo. Una excepción sería si usaste el lenguaje de una forma rara y sorprendente como, por ejemplo, sobrecargar el operador + sin la connotación de “más”. No obstante, en este caso deberías considerar cambiar el código y no el comentario.
  • No repitas código en tus comentarios. “Añadir cinco a x” no aporta ninguna información nueva a “x + 5”. Céntrate en el significado de x, por ejemplo “añade 5 metros a la posición x.
  • No olvides que identificadores (variables, tipos y nombres de funciones) ya deberían contener su semántica en si mismos. Un comentario puede todavía ser necesario para explicar el contexto en que una operación tiene lugar, por ejemplo un comentario a send_to_printer(document) podría ser «Aquí el documento debe estar en un formato adecuado para la impresora».
  • ¿Tiene importancia cuando el código o el comentario fue escrito y cuantas veces fue cambiado? Ten esto en cuenta antes de escribir una fecha en el código. “Nuevo” y “viejo” dan una idea de que algo fue cambiado, pero no cuando. No obstante, no tengas miedo en que comentarios podrían caducar en el próximo cambio de código. Todavía pueden dar una pista importante y un programador sabe que lo que hace un programa funcionar es el código y no el comentario.
  • No olvides limpiar el código. Muchos de los ejemplos anteriores se “olvidaron”. Eran marcadores durante el desarrollo que perdían su sentido cuando el trabajo concluyó.
  • Si tus ficheros usan una cabecera, sólo escribe cosas ahí para que habrá suficiente disciplina para actualizarlas. Un fichero guarda una fecha de última modificación, el último “autor” es probablemente uno de tu grupo de trabajo, y la versión del fichero fuente inútil. Un fichero ejecutable tiene un número de versión y todos los ficheros fuente para crearlo tienen este mismo número de versión automáticamente. A menos si vendes ficheros fuentes y no programas que funcionan.
  • No es mala idea de usar comentarios para separar código visualmente en secciones (con barras o incluso con títulos). Es sabio usar el mismo estilo para el mismo tipo de sección.

Referencias

Hay muchas formas de programar, pero no todas son igualmente comprensibles. Por eso, especialmente en compañías grandes, se suele elaborar una guía de estilo de programación que recoge reglas de codificación: como separar código en ficheros y directorios, como elegir a nombres para variables y funciones, cómo alinear la sangría de un bloque etc. Hay muchas versiones y ninguna es perfecta. En este artículo no queremos proponer una guía de estilo más, sino dar unos consejos como elaborar una.

Una guía de estilo de programación sirve para unificar la manera de crear código. Un código desconocido es más fácil de entender si las mismas cosas se hacen de la misma manera. Un programador nuevo en el proyecto tarda menos tiempo en entenderlo. Aunque puede ser obvio guardar una clase en un fichero con el mismo nombre, no es requerido por el lenguaje. Y lo que no está requerido se hace. Hay más que un caso que un proyecto en producción se llama “new Project” o lo que la IDE propone por defecto. Meter todo en un fichero es más rápido que usar varios ficheros y llamar a variables i, x y a requiere menos esfuerzo que buscar una solución más literaria.

También es cierto que una guía de estilo afecta a la programación como un plan de tareas de hogar a un piso de estudiantes: la limpieza impone el más guarro. Cualquier definición de estilo no sirve si los programadores no aportan la disciplina necesaria para emplearla. Si los supervisores se cortan a pedir a los programadores a reeditar un fichero porque, por ejemplo, el código está mal alineado, entonces el estilo acabará ser uno más o menos similar a la guía. Esto puede ser un problema si el estilo del código es un criterio de calidad – a veces hasta contractualmente fijado con el cliente.

Al mismo tiempo revela también el peligro de imponer un estilo demasiado exigente. Cuyo carácter tiene un sentido de orden, siempre buscará una manera ordenada de escribir código. Una persona desordenada va a pasarse de cualquier regla si no se supervisa constantemente. Por lo tanto es importante no pedir demasiado y pedir algo justificado. En grupos pequeños no podría ser mala idea de dejar a todos opinar sobre una propuesta de estilo que luego será obligatoria para todos.

Reglas que una guía no demasiadamente exigente podría establecer son:

  • Se define una clase por fichero y este fichero se llama igual que la clase.
  • Se define donde guardar los ficheros fuente y donde los ficheros compilados para poder usar un control de versiones.
  • Se define abrir llaves en una línea nueva – o se usa la notación de Kernighan and Ritchie(o de Java) con la llave { en la misma línea.
  • Se define la sangría como 4 espacios y se descarta tabuladores.
  • Se puede exigir poner comentarios en estilo de javadoc u otra herramienta de documentación automatizada.

Una buena guía de estilo ayuda a evitar errores desde el principio. Por ejemplo, pedir que se asigne NULL a un puntero tras liberar la memoria a que apunta no aparece en ninguna referencia del lenguaje. Sin embargo, esta medida puede ahorrar errores difíciles a localizar. Propuestas igualmente útiles pueden ser

  • Asignar un valor cero a cualquier identificador que apunta a un recurso liberado – que pueden ser un “handler” de una ventana, el id de un evento registrado, referencias a memoría u objetos dinámicamente creados.
  • Obligar el uso de un namespace o prefijos para evitar conflictos de nombres con bibliotecas estándares. Igualmente se puede prohibir el uso del guión bajo como primera letra de un nombre de una variable no privada.
  • Usar una forma estandarizada de formar identificadores como CamelCase, notación húngara o separar nombres compuestas por guión bajo. El estilo puede fijar si nombres que contienen abreviaturas las convierten en minúsculas – algo como loadXML o loadXml.
  • Respecto a los nombres puede haber un énfasis especial a letras y números que fácilmente se confunden como o y 0 o i, j, l, I y 1.
  • Se pueden fijar en qué orden aparecen miembros públicos y privados en una clase.

Como en todo, hay que evaluar el beneficio de más reglas que, en principio, sólo complican el trabajo. En todo caso, un estilo debe ser fijado al principio de un proyecto. Para una compañía pequeña puede ser una buena idea adherirse a un estilo popular como el de Sun para Java o él de la biblioteca STL para C++.

El lenguaje C (y C++) ofrece tres maneras de definir constantes. En este artículo queremos aclarar las diferencias entre #define, enum y const. Como veremos a continuación, casi siempre es preferible usar const.

#define CONSTANTE_DEFINE 5
enum T_ENUM { CONSTANTE_ENUM = 5 };
const int CONSTANTE_VARIABLE = 5;

Los tres valores CONSTANTE_DEFINE, CONSTANTE_ENUM y CONSTANTE_VARIABLE valen lo mismo (tienen el valor 5) pero tienen diferencias significativas a la hora de compilación.

La directiva #define es una palabra clave del pre-procesador. Técnicamente se podría decir que no forma parte del lenguaje C. Antes que el compilador ve el código, el pre-procesador ya a reemplazado la macro CONSTANTE_DEFINE por su definición – en nuestro caso el símbolo 5. Es decir, en todos los sitios donde aparece el identificador CONSTANTE_DEFINE, el compilador ve un 5 como si nosotros mismos hubiéramos escrito un 5 en estos sitios. Es posible que el compilador ni siquiera puede decirnos qué exactamente está mal en una compilación fracasa, porque no sabe nada del identificador CONSTANTE_DEFINE.

Como la directiva #define sólo tiene un significado textual y carece de cualquier información de tipo, es aconsejable evitar estas directivas y const siempre y cuando sea posible. Sin embargo, el hecho que #define no tiene un tipo asociado puede ser también una ventaja a la hora de hacer conversiones de tipos implícitas. No obstante, todavía es cuestionable si conversiones implícitas son deseables.

Los programadores saben que físicamente se codifica una constante definida con enum como un const intconceptualmente una constante definida con enum no tiene valor numérico asociado. El concepto es todo al contrario: usar identificadores más significativos como LUNES, MARTES, AZUL, VERDADERO en lugar de números en que uno tiene que saber que un «1» significa lunes, otro «1» azul y un tercero verdadero.

Por lo dicho conviene recordar que no hay nada mejor para representar el concepto de uno que el símbolo «1». Si queremos añadir un número específico entonces mejor ponemos este número en lugar de inventarnos una constante cuyo valor luego debemos buscar por ahí.

La razón por qué se pueden asignar valores específicos a constante de enumeración es para poder adaptarlas a valores exteriores. Por ejemplo, podría definir un tipo que encapsula constantes de error de HTML.

enum T_HTML_ERROR
{
    E_PAGE_NOT_FOUND = 404;
}

Por asignar de forma inteligente los valores a las constantes puedo asignar fácilmente un valor de tipo int a una variable de tipo T_HTML_ERROR mediante un cast. Por ponerle el nombre E_PAGE_NOT_FOUND al valor 404, será más fácil entender el código.  Sin embargo, todavía debo hacer un cast y por eso, en general, es un mejor estilo definir constantes con const int que luego se asignan a una variable de tipo int. Como ya hemos dicho, la idea de una constante enum es justamente no tener en cuenta su valor.

Una constante enum como LUNES no es completa en sí, sino forma parte del conjunto de constantes que forman un tipo de enumeración como T_DIA. Desde el punto de vista del lenguaje no tiene sentido asignar una constante a otra cosa que a una variable de este tipo de enumeración. En lenguajes más puristas como el Pascal es incluso prohibido convertir enumerativos a su representación numérica interna.

Finalmente queda la opción de definir constantes mediante const. Este método permite definir el tipo y el valor de la constante. Es incluso posible esconder el valor de la constante, definiéndola extern en el fichero de cabecera y asignar su valor en el fichero fuente. Aunque codificar constantes con const es la manera más limpia, suele ser la menos usada. La razón será más bien costumbre porque técnicamente hay poca razón de usar #define o enum.

El compilador puede comprobar el tipo de las constantes con const que las hace mejor que los #define. Al mismo tiempo permite las misma conversiones implícitas. Si no tengo claro si quiero codificar un número como entero o una cadena de texto, puedo usar un typedef para poder modificar también el tipo de la constante con facilidad.

Los enum sólo pueden representar valores enteros, mientras una constante puede tener cualquier tipo: un número flotante, una cadena de texto, una estructura compleja. Todo. Una constante const tiene una dirección de memoria, por lo cual se puede pasar un puntero a ella. El valor de una constante const puede ser público en el fichero de cabecera o escondido en el fichero fuente.

En fin, hemos visto las diferencias y concluimos que por regla general es preferible usar constantes declaradas por const. Las excepciones son:

  • Podemos usar #define cuando efectivamente necesitamos hacer algo antes de compilar. Por ejemplo, constantes de error que dependen de la plataforma, en general dentro de una compilación condicional con #if#endif.
  • Podemos usar enum si sólo queremos nombres sin necesidad de saber los valores con qué se representan internamente.

Por todo lo demás: const

Referencias

El lenguaje C++, como muchos otros lenguajes, tiene un operador de incremento pre-fix y post-fix. El operador pre-fix ++p tiene el mismo efecto sobre una variable entera int p como un incremento post-fix p++: el valor se incrementa en uno. La diferencia está que la expresión ++p evalúa al valor de p después del incremento (pre-fix como pre como primero), mientras la expresión p++ tiene el valor de p antes del incremento (post como después). Sin embargo, en muchas ocasiones no se hace uso de esta diferencia y simplemente se quiere añadir uno a la variable p.

No importa mucho qué operador se usa para tipos básicos, porque el compilador añade el cálculo de «más uno» en el código ejecutable donde hace falta. Sin embargo hay una diferencia importante cuando se usan operadores sobrecargados en clases. Lo mismo que vamos a demostrar aquí para un operador de incremento ++ vale también para un operador de decremento --.

Una típica implementación de un incremento sobrecargado sería algo así:

class MiClase
{
private:
    int mi_valor;
public:
    // El operador pre-fix (argumento void)
    MiClase& operator++(void)
    {
        // Increméntame internamente (también puedo usar ++mi_valor)
        mi_valor += 1;

        // Devuelve una referencia a mí mismo
        return *this;
    }

    // El operador post-fix (argumento int sin nombre)
    MiClase operator++(int)
    {
        // Guarda una copia en una instancia temporal
        MiClase temp(*this);

        // Increméntame internamente (también puedo usar ++mi_valor)
        mi_valor += 1;

        // Devuelve la copia temporal con el estado anterior
        return temp;
    }
};

A primera vista vemos que el pre-incremento es mucho más simple. Incrementa el estado de la clase y devuelve una referencia a la instancia.

El post-incremento es más complicado. Tengo que crear una copia para no perder el estado anterior que finalmente devuelvo también como copia y no como referencia. Es decir, tengo que llamar dos veces al constructor de copia cuando no necesito hacerlo para el operador pre-fix.

Por este motivo es preferible usar el operador pre-fix para incrementar y decrementar objetos. Aunque esta regla no sea necesario para variables de tipos básicos es preferible hacerlo para tener un estilo único.

Creo que la palabra clave const aumenta bastante las emociones que uno podrá tener sobre C++: cuando es indudablemente un elemento para elevar el estilo de programación, también es uno que puede complicar bastante la compilación. En este artículo ponemos el enfoque más al por qué de usar entidades constante y como implementarlos en programas reales. He descrito en otro artículo de como y donde se puede usar la palabra clave const.

El compilador puede comprobar si un programa intenta modificar un valor constante. Declarar objetos constantes es, por lo tanto, una ayuda para detectar posibles errores en el código. Como es preferible detectar errores durante el tiempo de compilación – ya que el compilador hace el trabajo de encontrarlos – es en general preferible de programar código que cumple lo que se llama «const-correctness«. Además, la palabra const indica al lector del código que el programador intentó sólo leer de un valor. Sin duda, usar objetos constantes incrementa considerablemente la calidad y el estilo del código.

También es cierto que un programa funciona sin constantes. Por eso hay tendencia de no usarlas. Lo gordo viene cuando uno empieza a usarlas en un código que en general no lo usa. Esto puede causar errores de compilación hasta en la n-ésima llamada anidada a una función, que por alguna razón requiere que un valor no sea constante – aunque sólo lo lea y no lo escribe. Más aún fascinan los errores de la STL (Standard Template Library), que puede causar mensajes de error kilométricos que realmente quieren decir algo como «No puedo convertir el tipo const_iterator en iterator.» Como caramelito adicional hay métodos que sólo existen para un tipo de iterador. Por ejemplo, el operador [] del contenedor mapa sólo acepta un iterador a un mapa modificable.

¿Cómo podemos poner orden en el caos? Pues, básicamente en usar wrappers. En nuestro código usamos constantes de forma correcta. Si tenemos que llamar a una función que no podemos o queremos modificar, entonces convertimos los datos de la constante a una variable. El caso más simple sería una simple asignación.

const int constante = 5;
int parametro = constante;
funcion_sin_const_correctness(parametro);

Si no podemos copiar el valor, entonces podemos usar dos técnicas. Una es la de C mediante punteros.

const int constante = 5;
int& parametro_referencia = *(int*)(void*)(&constante);

La otra es con la expresión de C++ const_cast<TIPO>:

const int constante = 5;
int& parametro_referencia = const_cast<int>(constante);

Esta construcción está en el estándar explícitamente para quitar el const. Por lo tanto es la más comprensible a la hora de hacerlo. Aunque el const_cast se parece visualmente a un patrón (template), no lo es. Es una expresión nativa de C++.

Toma nota que no he puesto una copia de la constante al parámetro sino el parámetro es una referencia al valor. Esto puede ser preferible porque ahorra la llamada a un constructor de copia, pero también incrementa el riesgo que un programa erróneo escribe en la constante.

Lo más complicado suele ser convertir los iteradores de la librería estándar de C++, ya que un const_iterator no es lo mismo que un iterator declarado const. Son dos clases distintas que pueden tener diferencias internas importantes. En este caso no suele quedar otra que hacer apaños.

  • Podemos tener la suerte que en nuestra distribución de la librería estándar se puede construir un iterator con un const_iterator. Esto sería lo más fácil.
  • Podemos averiguar si continuamos con un puntero al objeto en lugar del iterador. Es decir algo como
    Contenedor<int>::const_iterator iterador_constante;
    int* puntero_parametro = &*iterador_constante;
    

    Podemos aprovechar que los punteros también son iteradores – al menos para contenedores de acceso aleatorio como vector y deque y arrays de C.

  • Es posible también que sólo necesitamos el valor del elemento que podemos entonces convertir como los variables y objetos constantes mencionados más arriba.
  • Si tenemos un bucle podemos estudiar si la variable del bucle será un iterator en lugar de un const_iterator.

Aún así puede haber el caso en que no podemos hacer nada más que abandonar la const-correctness. Esto sucede especialmente cuando hemos pasado un const_iterator como parámetro de función. En este caso nos consolamos en haber hecho lo máximo posible aunque no era lo máximo deseable.

En general es preferible usar const porque ayuda a mejorar el estilo del código y reduce errores a la hora ejecución. En la vida real nos podemos enfrentar a situaciones difíciles cuando actualizamos código viejo. Sin embargo, podemos intentar adaptar el mejor estilo posible en cada momento. No suele ser ventajoso mejorar el estilo de todo el código de golpe. Pero si lo mejoramos gradualmente en las partes que estamos tocando, acabamos tener actualizaciones de calidad sin necesidad de tocar código que ya funciona aunque tenga peor estilo.

En este artículo nos dedicamos como podemos usar constantes en C++. Hay otro artículo que trata de por qué conviene usar const.

A primera vista sorprende cuantas cosas pueden se constantes en C++. Se pueden declarar const

  • variables,
  • punteros o las variables a que apuntan (doblemente const),
  • referencias,
  • clases,
  • instancias de clases o
  • sólo miembros de datos dentro de una clase y también
  • métodos de una clase.

Para complicar la cosa aún más se puede usar const para sustituir enum y #define.

Variables constantes

Una variable declarada const no se puede cambiar, es decir, es una constante. No obstante, la palabra const sólo se tiene en cuenta durante el tiempo de compilación. El compilador emite un error cuando encuentra una instrucción que quiere asignar un valor a una constante, pero físicamente el valor de una constante puede estar guardado en la memoria RAM y cuesta poco asignar otro valor a esta posición mediante un uso malintencionado de punteros y casts.

Cuando un compilador optimiza el código ejecutable, entonces puede optar por no leer el valor de la memoria sino incrustarlo directamente en las instrucciones del procesador. Así es posible que el valor en la memoria RAM cambie pero el programa sigue con el valor que el compilador determinó durante la compilación. No obstante, la dirección de la constante sigue siendo válida aunque el ejecutable no lee de ahí.

Tener una representación física en la memoria es lo que distingue una variable declarada const de constantes definidas por enum o #define. Es decir, puedo hacer esto

const int c = 5;
const int* p = &c;  // p apunta a la dirección de c. *p vale 5.

pero no puede hacer esto

enum { CONSTANTE_ENUM };
const enum* pe = &CONSTANTE_ENUM // Error de compilación

Esto es porque una constante de enum sola no tiene tipo sino sólo representa un valor. Lo que convierte un enum en un tipo es el conjunto de los valores de que está formado.

La cosa es aún más complicada con los #define. Es una directiva del preprocesador que reemplaza un texto por otro antes de que el código llegue al compilador. Quiere decir, los nombres de las macros definido por #define no existen para el compilador.

#define CONSTANTE 5
int a = CONSTANTE;  // El compilador ve "int a = 5".

Punteros

Los punteros son variables que guardan una dirección de memoria. Como cualquier variable su valor puede ser constante o no. Los punteros son especiales por no tener sólo un tipo de qué son – una dirección de memoria – pero por tener también otro tipo asociado del valor a que apuntan. Y este valor puede ser constante o variable de forma independiente. Por este motivo puede haber dos const en la definición de un puntero.

      int        variable = 1;
const int        constante = 2;
const int *      puntero_a_constante = &constante;
      int *const puntero_constante_a_variable = &variable;
const int *const puntero_constante_a_constante = &constante;
  • El puntero_a_constante puede apuntar a varios objetos, pero no puede modificarlos. Cadenas de caracteres se definen típicamente así: como const char*.
  • Un puntero_constante_a_variable puede modificar el objeto a que apunta, pero no puede apuntar a otra cosa.
  • Finalmente, un puntero_constante_a_constante ni puede modificar el objeto a que apunta ni apuntar a otro objeto. Por este punto de vista una cadena de texto hardcoded tiene realmente el tipo const char *const. Sin embargo, se usa poco.

Como nota quiero decir también que existe la notación

      int const* puntero

pero se usa menos. De hecho no conviene usarlo porque es más ambiguo: ¿el const se refiere al tipo (int) o a la dirección de memoria (el «*»)?

Referencias

Para las referencias valen básicamente las mismas reglas que para punteros. No obstante hay una importante diferencia: referencias no pueden referirse a otra instancia. Al contrario de punteros las referencias no pueden existir por si mismas sino deben inicializarse a la hora de ser declarada.

      int        variable = 1;
const int        constante = 2;
const int &      referencia_a_constante = constante;

// &const es permitido pero innecesario. Por eso nunca se usa.
      int &const referencia_constante_a_variable = variable;
const int &const referencia_constante_a_constante = constante;

Como las referencias apuntan al mismo objeto durante toda su vida, & y &const es lo mismo, y por eso nunca se escribe &const.

Es fácil hacer un cast para convertir un puntero de un objeto a otro objeto con un tipo diferente. Las referencias, en cambio, tienen el mismo tipo que el objeto a que se refieren y aportan más seguridad contra un cast implícito.  Por eso es preferible usar referencias en lugar de punteros siempre cuando sea posible.

Muy especialmente conviene usar referencias a constantes en lugar de constantes como parámetros de funciones. Se usan igual pero sólo requieren copiar un puntero en lugar de un objeto entero. Es decir, no se llamará un constructor para una referencia.

Clases constantes

Igual como se puede convertir un tipo simple como int a constante, se puede declarar const a una estructura compleja. La construcción const MiClase convierte todos los miembros de esta instancia en constantes. Sólo se les pueden asignar un valor en el constructor y después ya no. Se puede forzar una excepción para un campo en la clase declarándolo mutable, pero en general hay poca razón de hacerlo.

También es posible declarar campos individuales constantes dentro de una clase. Por ejemplo, puedo crear una clase que guarde una posición como una posición inicial constante y un desplazamiento variable.

class MiSitio
{
    double desplazamiento;          // una variable
    const double posicion_inicial;  // una constante
};

El valor de posicion_inicial sería asignado en el constructor de la clase y se quedaría inmodificable durante la vida de la instancia. No obstante, cada instancia puede tener otro valor para posicion_inicial. Por lo tanto un miembro constante no es los mismo que una constante global y común a todas las instancias.

Lo que queda por hacer constante son los métodos de una clase. Métodos constantes «prometen» de no modificar ningún dato dentro de la clase. (Más correctamente ninguno que no sea mutable.) Son los únicos métodos que puedo llamar para una instancia constante.

class MiCosa
{
public:
    int mi_variable;

    void mi_metodo_constante() const
    {
        mi_variable = 0;  // Error de compilación.
        // No debo modificar campos en un método constante.
    };

    void mi_metodo_variable()
    {
        mi_variable = 0;  // Ok
    };
};

const MiCosa cosa;
cosa.mi_metodo_constante();  // Ok
cosa.mi_metodo_variable();   // Error de compilación.
// No puedo llamar a un método NO constante para un objeto constante.

Para complicar la cosa aún más, se pueden definir dos métodos iguales en que una es constante y la otra no. Es algo que usa mucho para los métodos que devuelven iteradores de inicio y final en los contenedores de la STL.

iterator begin(void);
const_iterator begin(void) const;

El método constante devuelve un iterador (puntero) a un objeto constante, mientras la versión variable devuelve un iterador a un objeto variable.

Conclusión

La palabra clave const no es fácil de entender, porque se usa en muchos conceptos. Sin embargo, es la pieza que permite a C++ de ser uno de los pocos lenguajes a conseguir la «const-correctness«. Esta a su vez significa que ya el compilador puede comprobar que no se asigna un valor a algo que sea de sólo lectura. Aunque muchos programadores hacen caso omiso al const, conviene tener una noción de ellos cuando uno trabaja con la STL, ya que muchos errores de compilación (que suelen tener un mensaje largo) se deben a confundir iterator con const_iterator.

Aquellos que buscan más motivación puedo recomendar el artículo «Por qué usar const en C++«. Pero también hay consuelo para quienes buscan la manera de esquivar las constantes: puedo eliminar cualquier const con const_cast.

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 61 suscriptores

Archivos

May 2024
L M X J V S D
 12345
6789101112
13141516171819
20212223242526
2728293031