El uso de const con parámetros string
Como todos sabemos, Delphi permite utilizar el modificador const en la declaración de un parámetro. Esto tiene un doble efecto:
Comprueba que el implementador de la rutina no realice modificaciones directas sobre el parámetro. Como corolario, asegura al cliente de la rutina que no se realizarán modificaciones sobre el parámetro, por lo que puede pasar sin problemas variables a dicho parámetro.
El compilador puede optimizar el traspaso de parámetros cuya representación binaria tenga más de 4 bytes (las variables reales se tratan de forma diferente).
El último punto es muy importante para poder programar aplicaciones eficientes. Cuando un parámetro cuya representación ocupa más de 4 bytes se pasa por valor (es decir, sin especificar var o const), la rutina debe realizar una copia del mismo, para que si se realizan modificaciones sobre el parámetro, no sean visibles por el código que llama a la rutina. Y, por supuesto, esta copia es una operación potencialmente costosa.
Retrocedamos a la época de Delphi 1, cuando el tipo string se representaba como un array de 256 caracteres. Cada vez que se pasaba una cadena por valor a un procedimiento o función, se producía la copia de 256 bytes desde la variable original hacia la memoria de pila de la rutina. Sin embargo, si la cadena se hubiera pasado por referencia (utilizando var), solamente se hubiera pasado a la rutina el puntero a la cadena original, y la copia no se produciría. ¿El coste? Pues que la rutina podría potencialmente realizar modificaciones sobre la variable original (todo un riesgo), y que solamente podríamos pasar variables a la rutina, no constantes o expresiones en general.
Por eso Borland introdujo el modificador const, inspirado en C++. Este tipo de traspaso ofrece lo mejor de ambos mundos: permite pasar un puntero a la variable, lo cual es más eficiente. Si queremos pasar una expresión, el compilador genera una variable auxiliar oculta, evalúa la expresión y la almacena allí, y pasa la dirección de la temporal. Y se evita la copia de la cadena, pues el implementador tiene prohibido escribir sobre la cadena.
Hasta aquí bien, ¿no? El problema es que al aparecer Delphi 2 se cambió la representación de las cadenas de caracteres. En vez de representarlas como un array de caracteres directo, las nuevas versiones de Delphi y C++ Builder utilizan un puntero a dicho array. Tanto si utilizamos const como si no lo hacemos, Delphi pasa un puntero a la rutina, y no saca copia local de la cadena. ¿Tiene sentido seguir utilizado const? En primer lugar, recuerde que siguen existiendo tipos de datos en Delphi que pueden seguir beneficiándose de este modificador: los arrays y los records, por ejemplo. Pero sigue siendo beneficioso utilizar const con parámetros de cadenas.
¿Por qué? No es evidente, pero intentaré explicarlo en pocas palabras. Delphi evita la copia de la cadena manteniendo un contador de referencias para cada cadena. Al asignar una cadena a un parámetro o a cualquier otra variable, se incrementa este contador. Si alguien intenta modificar la cadena, Delphi comprueba que no haya más de una referencia a la misma. De haberlas, se produce lo que Borland denomina copy-on-demand, o copia por demanda: ahora sí se crea una nueva copia de la cadena, sobre la cual se realiza la modificación.
Por lo tanto, si declaramos un parámetro de cadena mediante el modificador const, nos ahorraremos el incremento y el decremento del contador de referencias del valor que pasemos, operación que normalmente realizaría la rutina implícitamente. Aunque esta actividad es menos costosa que una copia, si estamos llamando a la rutina en cuestión dentro de un bucle puede ahorrarnos tiempo y, en cualquier caso, espacio de código.
|