Ir al contenido

Diferencia entre revisiones de «Correctitud de constantes»

De Wikipedia, la enciclopedia libre
Contenido eliminado Contenido añadido
m Bot: 8 - Estandarizaciones y otras mejoras automatizadas
Corrijo ortografía, gramática, estilo y formato; traduzco categorías; retiro plantilla de mantenimiento, traducción revisada; cambios menores
Línea 1: Línea 1:
En [[programación]], la '''correctitud de constantes''' ({{lang-en|const correctness}}) es el tipo de [[correctitud]] que hace referencia a la adecuada declaración de [[Variable (programación)|variables]] u [[Objeto (programación)|objetos]] como [[Objeto inmutable|inmutables]]. El término es mayormente usado en el contexto de [[C (lenguaje de programación)|C]] o [[C++]], y recibe su nombre de la palabra reservada <code>const</code> de estos lenguajes.
{{Revisar traducción}}


El uso de la palabra reservada <code>const</code> indica lo que el programador «debe» hacer, no necesariamente lo que el programador «puede» hacer, pues calificar datos con esta palabra reservada no provoca que se almacenen en un tipo de [[Memoria (informática)|memoria]] de sólo lectura, sino que ordena al [[compilador]] realizar verificaciones sobre el [[Código fuente|código]] en [[tiempo de compilación]] para finalizar con un error el proceso de compilación en el caso de intentar modificar un dato constante.
En [[programación]], la '''correctitud de constantes''' (const correctness en Inglés) es el tipo de [[correctitud]] que hace referencia a la correcta declaración de variables u objetos como [[Objeto inmutable|inmutable]]s. El término es mayormente usado en el contexto de [[C (lenguaje de programación)|C]] o [[C++]], y recibe su nombre de la palabra reservada <code>const</code> de estos lenguajes.


El hecho de que sea posible modificar datos calificados con <code>const</code> en [[Tiempo de ejecución|tiempo de ejecución]] prueba que estos no se almacenan en memoria de sólo lectura. Para realizar estos cambios en tiempo de ejecución, deben evitarse las verificaciones que el compilador realiza sobre los valores <code>const</code> mediante el uso de [[Conversión de tipos|conversiones de tipo]] o [[Union (programación)|uniones]].
El uso de la palabra reservada <code>const</code> indica lo que el programador ''debe'' hacer, no necesariamente lo que el programador ''puede'' hacer, pues calificar datos con esta palabra reservada no provoca que se almacenen en un tipo de [[Memoria (informática)|memoria]] de sólo lectura, si no que ordena al [[compilador]] realizar verificaciones sobre el [[Código fuente|código]] en [[tiempo de compilación]] para finalizar con un error el proceso de compilación en el caso de intentar modificar un dato constante.

El hecho de que sea posible modificar datos calificados con <code>const</code> en [[Tiempo de ejecución|tiempo de ejecución]] prueba que estos no se almacenan en memoria de sólo lectura. Para realizar estos cambios en tiempo de ejecución deben evitarse las verificaciones que el compilador realiza sobre los valores <code>const</code> mediante el uso de [[Conversión de tipos|conversiones de tipo]] o [[Union (programación)|union]]es.


<source lang=cpp>const int constante = 0; // Valor constante
<source lang=cpp>const int constante = 0; // Valor constante
Línea 36: Línea 34:
*u.punteroA_No_Constante = 4;</source>
*u.punteroA_No_Constante = 4;</source>


Cabe destacar que el compilador puede decidir realizar [[Optimización de software|optimizaciones]] sobre los valores calificados con <code>const</code>, como por ejemplo utilizar el valor literal en lugar del valor almacenado en la variable, esta optimización es conocida como [[propagación de constantes]] y también se aplica sobre los literales de texto <code>const char *</code>; dado que no es posible modificar el valor de un literal, el ejemplo anterior daría lugar a un [[comportamiento no definido]].
Cabe destacar que el compilador puede decidir realizar [[Optimización de software|optimizaciones]] sobre los valores calificados con <code>const</code> —como, por ejemplo, utilizar el valor literal en lugar del valor almacenado en la variable—. Esta optimización es conocida como [[propagación de constantes]] y también se aplica sobre los literales de texto <code>const char *</code>; dado que no es posible modificar el valor de un literal, el ejemplo anterior daría lugar a un [[comportamiento no definido]].


Los [[Método (informática)|método]]s no estáticos pueden declararse como <code>const</code>, al hacerlo el [[This (programación)|puntero <code>this</code>]] dentro del método es de tipo <code>valor_de_retorno const * const</code> en lugar de <code>valor_de_retorno * const</code>. Esto significa que dentro del método constante el compilador tratará como error cualquier llamada a otros métodos no constantes o la modificación de cualquier [[Campo (informática)|campo]] del objeto.
Los [[Método (informática)|métodos]] no estáticos pueden declararse como <code>const</code>. Al hacerlo el [[This (programación)|puntero <code>this</code>]] dentro del método es de tipo <code>valor_de_retorno const * const</code> en lugar de <code>valor_de_retorno * const</code>. Esto significa que, dentro del método constante, el compilador tratará como error cualquier llamada a otros métodos no constantes o la modificación de cualquier [[Campo (informática)|campo]] del objeto.


En C++ un campo puede ser declarado como [[mutable (Informática)|<code>mutable</code>]], indicando que la anterior restricción no se aplica sobre él, en algunos casos esto puede ser útil, por ejemplo para [[Caché (informática)|cachear de datos]], [[recuento de referencias|conteo de referencias]] o [[sincronización de datos]]. En estos casos, no se altera el estado lógico del objeto, pero no es físicamente constante ya que su representación [[Sistema binario|binaria]] puede cambiar.
En C++, un campo puede ser declarado como [[mutable (Informática)|<code>mutable</code>]], indicando que la anterior restricción no se aplica sobre él. En algunos casos, esto puede ser útil, por ejemplo, para [[Caché (informática)|cachear de datos]], [[recuento de referencias|contar referencias]] o [[sincronización de datos|sincronizar datos]]. En estos casos, no se altera el estado lógico del objeto, pero no es físicamente constante porque su representación [[Sistema binario|binaria]] puede cambiar.


==Sintáxis de C (y lenguajes derivados)==
== Sintaxis de C y lenguajes derivados ==
En C y otros lenguajes derivados, todos los tipos de datos (incluyendo los definidos por el usuario) pueden ser declarados con <code>const</code>, una adecuada correctitud de constantes dicta que todas las variables u objetos deben ser declarados constantes a no ser que exista intención de modificarlos. Este uso pro-activo de <code>const</code> hace que los valores sean "más fáciles de entender, rastrear y razonar sobre ellos"<ref>[[Herb Sutter]] y [[Andrei Alexandrescu]] (2005). ''C++ Coding Standards''. p. 30. Boston: Addison Wesley. ISBN 0-321-11358-6</ref> además de mejorar la legibilidad y comprensión del código facilitando las tareas del equipo encargado del mantenimiento del código ya que el propio código muestra la intención de uso de cada valor.


En C y otros lenguajes derivados, todos los tipos de datos —incluyendo los definidos por el usuario— pueden ser declarados con <code>const</code>. Una correctitud de constantes dicta que todas las variables u objetos deben ser declarados constantes a no ser que exista intención de modificarlos. Este uso pro-activo de <code>const</code> hace que los valores sean «más fáciles de entender, rastrear y razonar sobre ellos»,<ref>[[Herb Sutter]] y [[Andrei Alexandrescu]] (2005). ''C++ Coding Standards''. p. 30. Boston: Addison Wesley. ISBN 0-321-11358-6</ref> además de mejorar la legibilidad y comprensión del código, facilitando las tareas del equipo encargado del mantenimiento del código, ya que el propio código muestra la intención de uso de cada valor.
===Tipos de datos simples===
Para tipos de datos simples (excepto punteros), el calificador <code>const</code> puede situarse en ambos lados del tipo (es decir, <code>const char foo = 'a';</code> equivale a <code>char const foo = 'a';</code>). En algunos compiladores usar <code>const</code> en ambos lados del tipo (por ejemplo, <code>const char const</code>) genera una advertencia pero no un error.


=== Tipos de datos simples ===
La flexibilidad para situar el calificador <code>const</code> existe por razones históricas,<ref>Preguntas frecuentes sobre C++ por [[Bjarne Stroustrup]] http://www.stroustrup.com/bs_faq2.html#constplacement</ref> el C y C++ previo a la estandarización imponía pocas o ninguna regla de ordenación en los calificadores de tipo, dado que usar el calificador antes o despues del tipo no provoca ambigüedades, se decidió no desarrollar una regla de ordenación para este caso.


Para tipos de datos simples —excepto punteros—, el calificador <code>const</code> puede situarse en ambos lados del tipo —es decir, <code>const char foo = 'a';</code> equivale a <code>char const foo = 'a';</code>. En algunos [[compilador]]es, usar <code>const</code> en ambos lados del tipo —por ejemplo, <code>const char const</code> genera una advertencia pero no un error.
===Punteros y referencias===

El significado de <code>const</code> es más complicado para [[Puntero (informática)|puntero]]s y [[Referencia (informática)|referencia]]s (tanto el puntero como el dato apuntado, incluso ambos, pueden ser calificados con <code>const</code>). Además, la sintaxis puede ser confusa. Pueden declararse punteros constantes que apunten a datos no constantes, o punteros modificables que apunten a datos no modificables, o punteros constantes a valores constantes. No se puede cambiar el dato al que apunta un puntero constante, pero puede modificarse el dato apuntado. Por el contrario, puede modificarse el dato al que apunta un puntero constante (que debe ser un dato del mismo tipo o un tipo convertible al del puntero), pero no puede modificarse el valor apuntado a través del mismo. Finalmente, puede declararse un puntero constante a un dato constante, el cuál no puede cambiarse el dato al que apunta ni modificar el valor del dato apuntado a través del mismo. El siguiente ejemplo ilustra estos matices:
La flexibilidad para situar el calificador <code>const</code> existe por razones históricas<ref>Preguntas frecuentes sobre C++ por [[Bjarne Stroustrup]] http://www.stroustrup.com/bs_faq2.html#constplacement</ref> —el C y C++ previos a la estandarización imponían pocas, o ninguna regla de ordenación en los calificadores de tipo—. Dado que usar el calificador antes o después del tipo no provoca ambigüedades, se decidió no desarrollar una regla de ordenación para este caso.

=== Punteros y referencias ===

El significado de <code>const</code> es más complicado para [[Puntero (informática)|punteros]] y [[Referencia (informática)|referencias]] —tanto el puntero como el dato apuntado, incluso ambos, pueden ser calificados con <code>const</code>. Además, la sintaxis puede ser confusa. Pueden declararse punteros constantes que apunten a datos no constantes, o punteros modificables que apunten a datos no modificables, o punteros constantes a valores constantes. No se puede cambiar el dato al que apunta un puntero constante, pero puede modificarse el dato apuntado. Por el contrario, puede modificarse el dato al que apunta un puntero constante —que debe ser un dato del mismo tipo o un tipo convertible al del puntero—, pero no puede modificarse el valor apuntado a través del mismo. Finalmente, puede declararse un puntero constante a un dato constante, pero no puede cambiarse el dato al que apunta ni modificar el valor del dato apuntado a través del mismo. El siguiente ejemplo ilustra estos matices:


<source lang=c>
<source lang=c>
Línea 72: Línea 73:
}</source>
}</source>


Según las convenciones de C para declaraciones, la declaración sigue al uso, por lo que escribir <code>*</code> sobre un puntero indica su desreferenciación. Así pues, en la declaración <code>int *puntero</code>, al desreferenciar el puntero <code>*puntero</code> se obtiene <code>int</code>, mientras que <code>puntero</code> es un puntero a <code>int</code>. En consecuencia <code>const</code> modifica el identificador a su derecha.
Según las convenciones de C para declaraciones, la declaración sigue al uso, por lo que escribir <code>*</code> sobre un puntero indica su desreferenciación. Así pues, en la declaración <code>int *puntero</code>, al desreferenciar el puntero <code>*puntero</code> se obtiene <code>int</code>, mientras que <code>puntero</code> es un puntero a <code>int</code>. En consecuencia, <code>const</code> modifica el identificador a su derecha.


Sin embargo, las convenciones de C++ asocian el <code>*</code> con el tipo, como en <code>int* puntero</code> y <code>const</code> modifica el tipo a la izquierda. <code>int const * punteroAConstante</code> puede leerse como "<code>*punteroAConstante</code> es un <code>int const</code>" (el valor es constante), o "<code>punteroAConstante</code> es un <code>int const *</code>" (el puntero apunta a un entero constante):
Sin embargo, las convenciones de C++ asocian el <code>*</code> con el tipo, como en <code>int* puntero</code> y <code>const</code> modifica el tipo a la izquierda. <code>int const * punteroAConstante</code> puede leerse como <code>*punteroAConstante</code> es un <code>int const</code> —el valor es constante—, o <code>punteroAConstante</code> es un <code>int const *</code> —el puntero apunta a un entero constante—:


<source lang=c>int *puntero; // *puntero es un valor int
<source lang=c>int *puntero; // *puntero es un valor int
Línea 85: Línea 86:
Según la convención de C++ de analizar el tipo, no el valor, la [[rule of thumb|regla general]] es leer la declaración de derecha a izquierda. Así pues, todo lo que quede a la izquierda del [[asterisco]] pertenece al tipo apuntado y aquello a la derecha del mismo pertenece a las propiedades del puntero. En el anterior ejemplo, <code>int const *</code> puede ser interpretado como un puntero de sólo lectura que apunta a un entero de lectura-escritura.
Según la convención de C++ de analizar el tipo, no el valor, la [[rule of thumb|regla general]] es leer la declaración de derecha a izquierda. Así pues, todo lo que quede a la izquierda del [[asterisco]] pertenece al tipo apuntado y aquello a la derecha del mismo pertenece a las propiedades del puntero. En el anterior ejemplo, <code>int const *</code> puede ser interpretado como un puntero de sólo lectura que apunta a un entero de lectura-escritura.


También está permitido situar <code>const</code> a la izquierda del tipo en C y C++, la siguiente sintaxis:
También está permitido situar <code>const</code> a la izquierda del tipo en C y C++, como en la siguiente sintaxis:


<source lang="cpp">const int* punteroAConst; // identico a: int const * punteroAConst
<source lang="cpp">const int* punteroAConst; // identico a: int const * punteroAConst
Línea 97: Línea 98:
const int* const punteroConstanteAConst;</source>
const int* const punteroConstanteAConst;</source>


Las [[Referencia (informática)|referencias]] de C++ siguen normas similares. Declarar una referencia <code>const</code> es redundante ya que no es posible hacer que una referencia apunte a otro objeto:
Las [[Referencia (informática)|referencias]] de C++ siguen normas similares. Declarar una referencia <code>const</code> es redundante, ya que no es posible hacer que una referencia apunte a otro objeto:


<source lang=cpp>int i = 22;
<source lang=cpp>int i = 22;
int const & referenciaAConst = i; // Correcto
int const & referenciaAConst = i; // Correcto
int & const referenciaConstante = i; // Error el "const" es redundante</source>
int & const referenciaConstante = i; // Error, el "const" es redundante</source>


Se pueden obtener declaraciones realmente complicadas usando [[array|vectores]] multidimensionales y referencias (o punteros) a punteros; sin embargo, estos usos se consideran confusos y propensos a errores, en consecuencia se aconseja evitarlos o reemplazarlos por estructuras de mayor nivel de [[Abstracción (informática)|abstracción]].
Se pueden obtener declaraciones realmente complicadas usando [[array|vectores]] multidimensionales y referencias —o punteros— a punteros; sin embargo, estos usos se consideran confusos y propensos a errores; en consecuencia, se aconseja evitarlos o reemplazarlos por estructuras de mayor nivel de [[Abstracción (informática)|abstracción]].


===Parámetros y variables===
=== Parámetros y variables ===
<code>const</code> puede ser usado tanto en parámetros de función como en variables ([[Variable estática|estáticas]] o automáticas, incluyendo globales o locales). La interpretación varía entre usos. Una variable <code>const</code> estática (variable global o variable estática local) es una constante, y debe usarse para representar datos como constantes matemáticas, por ejemlo: <code>const double PI = 3.14159</code> o parámetros generales en tiempo de compilación. Una variable <code>const</code> automática (variable local no estática) debe ser [[inicialización|inicializada]], aunque puede usarse un valor diferente en cada llamada, por ejemplo <code>const int x_al_cuadrado = x*x;</code>. Un parámetro pasado como referencia <code>const</code> indica que el valor referenciado no se modificará (forma parte del [[Diseño por contrato (informática)|contrato]]). Por ello, se suele favorecer el uso de <code>const</code> en parámetros pasados por referencia pero no en los parámetros pasados por valor.


<code>const</code> puede ser usado tanto en parámetros de función como en variables [[Variable estática|estáticas]] o automáticas, incluyendo globales o locales. La interpretación varía entre usos. Una variable <code>const</code> estática —variable global o variable estática local— es una constante, y debe usarse para representar datos como constantes matemáticas —por ejemplo, <code>const double PI = 3.14159</code> o parámetros generales en tiempo de compilación. Una variable <code>const</code> automática —variable local no estática— debe ser [[inicialización|inicializada]], aunque puede usarse un valor diferente en cada llamada —por ejemplo, <code>const int x_al_cuadrado = x*x;</code>. Un parámetro pasado como referencia <code>const</code> indica que el valor referenciado no se modificará, pues forma parte del [[Diseño por contrato (informática)|contrato]]. Por ello, se suele favorecer el uso de <code>const</code> en parámetros pasados por referencia, pero no en los parámetros pasados por valor.
===Métodos===
Para aprovechar el diseño por contrato en tipos definidos por el usuario (estructuras y clases), que pueden tener tanto métodos como campos, el programador debe marcar los métodos de instancia como <code>const</code> si no tiene intención de modificar los campos del objeto. Usar el calificador <code>const</code> sobre los métodos de instancia que lo requieran es esencial para cumplir con la correctitud de constantes, aunque este tipo de correctitud no esté disponible en otros lenguajes de [[Programación orientada a objetos|programación orientada a objetos]] como [[Java (lenguaje de programación)|Java]] y [[C Sharp|C#]] o en la [[interfaz de línea de comandos|CLI]] de [[Microsoft]] o en las [[Managed Extensions for C++]].


=== Métodos ===
Los métodos calificados con <code>const</code> pueden ser llamados desde objetos constantes y no constantes indistintamente mientras que los métodos sin calificación <code>const</code> sólo pueden llamarse desde objetos no constantes.


Para aprovechar el diseño por contrato en tipos definidos por el usuario —estructuras y clases—, que pueden tener tanto métodos como campos, el programador debe marcar los métodos de instancia como <code>const</code> si no tiene intención de modificar los campos del objeto. Usar el calificador <code>const</code> sobre los métodos de instancia que lo requieran es esencial para cumplir con la correctitud de constantes, aunque este tipo de correctitud no esté disponible en otros lenguajes de [[Programación orientada a objetos|programación orientada a objetos]], como [[Java (lenguaje de programación)|Java]] y [[C Sharp|C#]], en la [[interfaz de línea de comandos|CLI]] de [[Microsoft]], o en las [[Managed Extensions for C++]].
El calificador <code>const</code> aplicado sobre un método de instancia afecta al objeto apuntado por el puntero <code>this</code>, que es un argumento implícito pasado a todos los métodos de instancia. Por lo tanto, los métodos constantes es la manera de aplicar la correctitud de constantes al parámetro implícito "<code>this</code>". Por ejemplo:

Los métodos calificados con <code>const</code> pueden ser llamados desde objetos constantes y no constantes indistintamente, mientras que los métodos sin calificación <code>const</code> sólo pueden llamarse desde objetos no constantes.

El calificador <code>const</code> aplicado sobre un método de instancia afecta al objeto apuntado por el puntero <code>this</code>, que es un argumento implícito pasado a todos los métodos de instancia. Por lo tanto, los métodos constantes son la manera de aplicar la correctitud de constantes al parámetro implícito <code>this</code>. Por ejemplo:


<source lang="cpp">class C
<source lang="cpp">class C
Línea 135: Línea 138:
</source>
</source>


En el ejemplo anterior, el puntero implícito "<code>this</code>" del método <code>Set()</code> tiene el tipo "<code>C *const</code>"; mientras que el "<code>this</code>" de <code>Get()</code> es de tipo "<code>const C *const</code>", lo cuál indica que ese método no puede modificar el objeto mediante el puntero "<code>this</code>.
En el ejemplo anterior, el puntero implícito <code>this</code> del método <code>Set()</code> tiene el tipo <code>C *const</code>; mientras que el <code>this</code> de <code>Get()</code> es de tipo <code>const C *const</code>, lo cual indica que ese método no puede modificar el objeto mediante el puntero <code>this</code>.

Es posible declarar un método con el mismo nombre pero con diferente calificación <code>const</code> (y posiblemente diferente uso) para adaptarse a ambos tipos de llamadas. Por ejemplo:
Es posible declarar un método con el mismo nombre pero con diferente calificación <code>const</code> —y, posiblemente, diferente uso— para adaptarse a ambos tipos de llamadas. Por ejemplo:


<source lang="cpp">
<source lang="cpp">
Línea 157: Línea 161:
</source>
</source>


La calificación <code>const</code> del objeto determina qué versión de <code>MiVector::Get()</code> será llamada y si se obtendrá una referencia que pueda ser modificada o de sólo lectura.
La calificación <code>const</code> del objeto determina qué versión de <code>MiVector::Get()</code> será llamada, y si se obtendrá una referencia que pueda ser modificada o de sólo lectura.

Técnicamente ambos métodos tienen diferentes [[firma___busca (informática)|firmas]] ya que sus punteros "<code>this</code>" tienen diferentes tipos, esto permite al compilador escoger el método correcto.
Técnicamente, ambos métodos tienen diferentes [[firma___busca (informática)|firmas]], ya que sus punteros <code>this</code> tienen diferentes tipos. Esto permite al compilador escoger el método correcto.

=== Excepciones a la correctitud de constantes ===


===Excepciones a la correctitud de constantes===
Existen varias excepciones a la correctitud de constantes puras en C y C++, principalmente, por [[retrocompatibilidad]] de código.
Existen varias excepciones a la correctitud de constantes puras en C y C++. Principalmente por [[retrocompatibilidad]] de código.


La primera excepción, tan sólo aplicable a C++, es el uso de <code>const_cast</code>, que permite eliminar la calificación <code>const</code> de un dato, haciéndolo modificable.
La primera excepción, tan sólo aplicable a C++, es el uso de <code>const_cast</code>, que permite eliminar la calificación <code>const</code> de un dato, haciéndolo modificable. La necesidad de eliminar la calificación <code>const</code> surge del uso de [[código heredado]], o de [[Biblioteca (informática)|bibliotecas]] que no pueden cambiarse pero que no cumplen con la correctitud de constantes. Por ejemplo:
La necesidad de eliminar la calificación <code>const</code> surge del uso de [[código heredado]] o [[Biblioteca (informática)|librerías]] que no pueden ser cambiadas pero que no cumplen con la correctitud de constantes. Por ejemplo:


<source lang="cpp">
<source lang="cpp">
Línea 183: Línea 188:
En el ejemplo anterior, si el puntero <code>ptr</code> apunta a una variable global, local, o campo calificado con <code>const</code>, o a un objeto creado dinámicamente mediante <code>new const int<code>, el código sólo será correcto si la función <code>LibraryFunc</code> realmente no modifica el valor apuntado por <code>ptr</code>.
En el ejemplo anterior, si el puntero <code>ptr</code> apunta a una variable global, local, o campo calificado con <code>const</code>, o a un objeto creado dinámicamente mediante <code>new const int<code>, el código sólo será correcto si la función <code>LibraryFunc</code> realmente no modifica el valor apuntado por <code>ptr</code>.


Existe una excepción{{Citation needed|reason=no está claro por qué es una excepción - podría ser considerado como una característica del lenguaje|date=February 2013}} que se aplica tanto a C como a C++: Los campos puntero o referencia “ignoran” la calificación <code>const</code> de sus propietarios (es decir, en un objeto constante todos sus miembros son constantes excepto en el caso de punteros y referencias), consideremos el siguiente código:
Existe una excepción{{Citation needed|reason=no está claro por qué es una excepción - podría ser considerado como una característica del lenguaje|date=February 2013}} que se aplica tanto a C como a C++: los campos puntero o referencia «ignoran» la calificación <code>const</code> de sus propietarios —es decir, en un objeto constante, todos sus miembros son constantes, excepto en el caso de punteros y referencias—. Considérese el siguiente código:


<source lang="cpp">
<source lang="cpp">
Línea 202: Línea 207:
</source>
</source>


Aunque el objeto <code>e</code> pasado a <code>Foo()</code> sea constante, haciendo que todos sus campos sean constantes, el dato apuntado por <code>e.puntero</code> puede ser modificado, pese a que no es adecuado desde el punto de vista de la correctitud de constantes ya que podría darse el caso de que <code>e</code> fuese propietario del dato apuntado.
Aunque el objeto <code>e</code> pasado a <code>Foo()</code> sea constante, lo que implica que todos sus campos sean constantes, el dato apuntado por <code>e.puntero</code> puede ser modificado pese a que no es adecuado desde el punto de vista de la correctitud de constantes —podría darse el caso de que <code>e</code> fuese propietario del dato apuntado—.


Por este motivo, se ha argumentado que la calificación por defecto para punteros y referencias miembro debe ser una correctitud de constantes más profunda, pudiendo modificar el comportamiento por defecto con el calificador <code>mutable</code> cuando el dato apuntado no pertenezca al objeto contenedor, pero aplicar este cambio crearía problemas de compatibilidad con código existente. Aún así, por motivos históricos{{Citation needed|reason=no está claro por qué no se aceptó la sugerencia|date=February 2010}}, esta excepción permanece en C y C++.
Por este motivo, se ha argumentado que la calificación por defecto para punteros y referencias miembro debe ser una correctitud de constantes más profunda, pudiendo modificar el comportamiento por defecto con el calificador <code>mutable</code> cuando el dato apuntado no pertenezca al objeto contenedor, pero aplicar este cambio crearía problemas de compatibilidad con código existente. Aun así, esta excepción permanece en C y C++.


La anterior excepción puede ser corregida usando una clase que oculte el puntero tras una interfaz que cumpla con la correctitud de constantes, pero estas clases tampoco soportan las semánticas habituales de copia desde un objeto calificado con <code>const</code> (lo que implica que la clase contenedora tampoco puede ser copiada con las semánticas de copia habituales) o permite que otras excepciones ignoren la correctitud de constantes mediante copia involuntaria o intencionada.
La anterior excepción puede ser corregida usando una clase que oculte el puntero tras una interfaz que cumpla con la correctitud de constantes; no obstante, estas clases tampoco soportan las semánticas habituales de copia desde un objeto calificado con <code>const</code> —lo que implica que la clase contenedora tampoco puede ser copiada con las semánticas de copia habituales—, ni permiten que otras excepciones ignoren la correctitud de constantes mediante copia involuntaria o intencionada.


Finalmente, varias funciones en la [[Biblioteca estándar de C|biblioteca estándar de C]] no cumplen con la correctitud de constantes, dado que reciben punteros <code>const</code> a cadenas de caracteres y devolviendo un puntero no-<code>const</code> a una parte de la misma cadena.
Finalmente, varias funciones en la [[Biblioteca estándar de C|biblioteca estándar de C]] no cumplen con la correctitud de constantes, dado que reciben punteros <code>const</code> a cadenas de caracteres y devuelven un puntero no-<code>const</code> a una parte de la misma cadena.
Algunas [[Implementación|implementaciones]] de la librería C++ estándar, como la de Microsoft<ref>[http://msdn.microsoft.com/en-us/library/b34ccac3%28VS.80%29.aspx documentación de strchr de Microsoft]</ref> intentan corregir estas excepciones proporcionando dos versiones de algunas funciones: una versión con "<code>const</code>" y otra sin.
Algunas [[Implementación|implementaciones]] de la biblioteca C++ estándar, como la de [[Microsoft]],<ref>[http://msdn.microsoft.com/en-us/library/b34ccac3%28VS.80%29.aspx documentación de strchr de Microsoft]</ref> intentan corregir estas excepciones proporcionando dos versiones de algunas funciones: una versión con <code>const</code>, y otra sin ello.


===correctitud de datos volátiles===
=== Correctitud de datos volátiles ===
El cualificador <code>volatile</code> de C y C++, indica que un objeto puede ser cambiado externamente en cualquier momento por medios ajenos al programa y por lo tanto debe volverse a leer de la memoria cuando se accede a él.
Este calificador se suele encontrar en código que manipula [[hardware]] directamente (en [[Sistema_embebido|sistemas embebidos]] y [[Driver|controladores de dispositivos]]) y en aplicaciones [[Thread|multihilo]] (aunque se suele usar de manera incorrecta en este contexto{{Citation needed|reason=harían falta ejemplos|date=August 2013}}).


El cualificador <code>volatile</code> de C y C++ indica que un objeto puede ser cambiado externamente en cualquier momento por medios ajenos al programa y, por lo tanto, debe volverse a leer de la memoria cuando se accede a él.
<code>volatile</code> se puede usar de la misma manera que <code>const</code> en declaraciones de variables, punteros, referencias y métodos, de hecho <code>volatile</code> es usado en ocasiones para implementar estrategias de diseño por contrato que [[Andrei Alexandrescu]] denomina correctitud de volátiles,<ref>[http://www.drdobbs.com/cpp/184403766 "Generic<Programming>: volatile — Multithreaded Programmer's Best Friend Volatile-Correctness or How to Have Your Compiler Detect Race Conditions for You"] por Andrei Alexandrescu en ''[[C/C++ Users Journal]]'' Foro de expertos C++</ref> aunque es mucho menos común que la correctitud de constantes. El calificador <code>volatile</code> también puede ser eliminado mediante <code>const_cast</code>, y puede combinarse con el calificador <code>const</code> como puede verse en este ejemplo:
Este calificador se suele encontrar en código que manipula [[hardware]] directamente —en [[Sistema_embebido|sistemas embebidos]] y [[Driver|controladores de dispositivos]] y en aplicaciones [[Thread|multihilo]].

<code>volatile</code> se puede usar de la misma manera que <code>const</code> en declaraciones de variables, punteros, referencias y métodos. De hecho, <code>volatile</code> es usado en ocasiones para implementar estrategias de diseño por contrato que [[Andrei Alexandrescu]] denomina «correctitud de volátiles»,<ref>[http://www.drdobbs.com/cpp/184403766 "Generic<Programming>: volatile — Multithreaded Programmer's Best Friend Volatile-Correctness or How to Have Your Compiler Detect Race Conditions for You"] por Andrei Alexandrescu en ''[[C/C++ Users Journal]]'' Foro de expertos C++</ref> aunque es mucho menos común que la correctitud de constantes. El calificador <code>volatile</code> también puede ser eliminado mediante <code>const_cast</code>, y puede combinarse con el calificador <code>const</code> como puede verse en este ejemplo:


<source lang="cpp">// Configura una referencia a un registro de hardware de solo lectura
<source lang="cpp">// Configura una referencia a un registro de hardware de solo lectura
Línea 228: Línea 234:
Dado que <code>registroDeHardware</code> es <code>volatile</code>, aunque el programador no pueda modificar su valor, no existen garantías de que mantenga los mismos datos en lecturas sucesivas. Las semánticas de esta variable indican que es de sólo lectura pero no inmutable.
Dado que <code>registroDeHardware</code> es <code>volatile</code>, aunque el programador no pueda modificar su valor, no existen garantías de que mantenga los mismos datos en lecturas sucesivas. Las semánticas de esta variable indican que es de sólo lectura pero no inmutable.


==<code>const</code> e <code>immutable</code> en D==
== <code>const</code> e <code>immutable</code> en D ==

En la segunda versión del [[D (lenguaje de programación)|lenguaje D]] exiten dos [[Palabra reservada|palabras reservadas]] relacionadas con la correctitud de constantes.<ref>http://www.digitalmars.com/d/2.0/const-faq.html#const</ref> la palabra reservada <code>immutable</code> denota un dato que no puede ser modificado a través de ninguna referencia.
En la segunda versión del [[D (lenguaje de programación)|lenguaje D]], exiten dos [[Palabra reservada|palabras reservadas]] relacionadas con la correctitud de constantes:<ref>http://www.digitalmars.com/d/2.0/const-faq.html#const</ref>
La palabra reservada <code>const</code> se refiere a una referencia inmutable de un dato mutable.
* la palabra reservada <code>immutable</code> denota un dato que no puede ser modificado a través de ninguna referencia;
Al contrario que el <code>const</code> de C++, <code>const</code> e <code>immutable</code> de D son [[relación transitiva|transitivos]], y cualquier dato alcanzable a través de <code>const</code> o <code>immutable</code> es <code>const</code> o <code>immutable</code> respectivamente.
* la palabra reservada <code>const</code> se refiere a una referencia inmutable de un dato mutable.

Al contrario que el <code>const</code> de C++, <code>const</code> e <code>immutable</code> de D son [[relación transitiva|transitivos]], y cualquier dato alcanzable a través de <code>const</code> o <code>immutable</code> es <code>const</code> o <code>immutable</code>, respectivamente.

=== Ejemplo de const e immutable en D ===


'''Ejemplo de const e immutable en D'''
<source lang = "D">int[] foo = new int[5]; // foo es mutable.
<source lang = "D">int[] foo = new int[5]; // foo es mutable.
const int[] bar = foo; // bar apunta a foo de manera inmutable.
const int[] bar = foo; // bar apunta a foo de manera inmutable.
Línea 242: Línea 252:
int[] mutableNums = nums; // Error: No se puede crear una referencia mutable a un dato inmutable.</source>
int[] mutableNums = nums; // Error: No se puede crear una referencia mutable a un dato inmutable.</source>


'''Ejemplo de const transitivo en D'''
=== Ejemplo de const transitivo en D ===

<source lang = "D">class Foo {
<source lang = "D">class Foo {
Foo next;
Foo next;
Línea 253: Línea 264:


== <code>final</code> en Java ==
== <code>final</code> en Java ==

En [[Java (lenguaje de programación)|Java]], el calificador <code>final</code> indica que el campo o variable no es asignable, por ejemplo:
En [[Java (lenguaje de programación)|Java]], el calificador <code>final</code> indica que el campo o variable no es asignable. Por ejemplo:
<source lang="java">final int i = 3;
<source lang="java">final int i = 3;
i = 4; // Error! No se puede modificar un objeto "final"</source>
i = 4; // Error! No se puede modificar un objeto "final"</source>
Línea 264: Línea 276:
Respecto a los punteros, una referencia <code>final</code> en Java tiene un significado similar a un puntero constante en C++.
Respecto a los punteros, una referencia <code>final</code> en Java tiene un significado similar a un puntero constante en C++.
<source lang="cpp">Foo *const bar = direccion_de_memoria; // puntero constante</source>
<source lang="cpp">Foo *const bar = direccion_de_memoria; // puntero constante</source>
En el ejemplo anterior <code>bar</code> debe ser inicializado en el momento de la declaración y no puede cambiar su valor en adelante, pero el valor al que apunta ''es'' modificable. Es decir <code>*bar = ''valor''</code> es válido.
En el ejemplo anterior, <code>bar</code> debe ser inicializado en el momento de la declaración y no puede cambiar su valor en adelante, pero el valor al que apunta es modificable. Es decir, <code>*bar = ''valor''</code> es válido, solo que no puede cambiar el lugar al que apunta. Las referencias finales de Java funcionan de la misma manera, con la particularidad de que no es necesario inicializarlas.
Simplemente no puede cambiar el lugar al que apunta. Las referencias finales de Java funcionan de la misma manera con la particularidad de que no es necesario inicializarlas.
<source lang="java">final Foo i; // una declaracion Java</source>
<source lang="java">final Foo i; // una declaracion Java</source>
Nota: Java no usa punteros.<ref>[http://java.sun.com/docs/white/langenv/Simple.doc2.html#4107 No More Pointers in Java]</ref>
Nótese que Java no usa punteros.<ref>[http://java.sun.com/docs/white/langenv/Simple.doc2.html#4107 No More Pointers in Java]</ref>


También se puede declarar un puntero a datos de "sólo lectura" en C++.
También se puede declarar un puntero a datos de sólo lectura en C++.
<source lang="cpp">const Foo *bar;</source>
<source lang="cpp">const Foo *bar;</source>
En el ejemplo anterior <code>bar</code> puede modificarse para que apunte a cualquier dato de tipo <code>Foo</code>; sin embargo el valor apuntado no podrá ser modificado mediante el puntero <code>bar</code>. No existe un mecanismo equivalente en Java. Así pues tampoco existen métodos <code>const</code>.
En el ejemplo anterior, <code>bar</code> puede modificarse para que apunte a cualquier dato de tipo <code>Foo</code>; sin embargo el valor apuntado no podrá ser modificado mediante el puntero <code>bar</code>. No existe un mecanismo equivalente en Java. Así pues, tampoco existen métodos <code>const</code>.

No se puede forzar la correctitud de constantes en Java, sin embargo, definiendo interfaces de sólo lectura a una clase, se puede garantizar que los objetos pueden ser utilizados sin que sea posible modificarlos. El [[Framework|framework]] de colecciones de Java proporciona mecanismos para crear un [[Adapter (patrón de diseño)|adapador]] sobre una <code>Collection</code> mediante <code>Collections.unmodifiableCollection()</code> y métodos similares.
No se puede forzar la correctitud de constantes en Java. Sin embargo, definiendo interfaces de sólo lectura a una clase se garantiza que los objetos puedan ser utilizados sin posibilida de modificarlos. El ''[[Framework|framework]]'' de colecciones de Java proporciona mecanismos para crear un [[Adapter (patrón de diseño)|adapador]] sobre una <code>Collection</code> mediante <code>Collections.unmodifiableCollection()</code> y métodos similares.

Los métodos en Java pueden ser declarados como <code>final</code>, pero este tipo de declaración no tiene relación con la correctitud de constantes sino con la herencia —significa que el método no puede ser [[Sobreescritura de Métodos|sobreescrito]] en clases derivadas—.


El lenguaje Java marca <code>const</code> como palabra reservada —por lo que no puede usarse como nombre de variable—, pero no le asocia semántica alguna. Esta palabra clave fue reservada para desarrollar una extensión del lenguaje Java que incluyese métodos <code>const</code> y punteros a tipos <code>const</code> al estilo C++{{Citation needed|date=February 2011}}. Existe una petición para implementar correctitud de constantes en [[Java Community Process|el Proceso de la Comunidad Java]], pero fue cerrada en 2005 indicando que es imposible implementar dicho cambio de manera que sea retrocompatible.<ref>[http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4211070 Ticket for adding const parameters in Java]</ref>
Los métodos en Java pueden ser declarados como "<code>final</code>", pero este tipo de declaración no tiene relación con la correctitud de constantes sino con la herencia (significa que el método no puede ser [[Sobreescritura de Métodos|sobreescrito]] en clases derivadas). Curiosamente, el lenguaje Java marca <code>const</code> como palabra reservada (no puede usarse como nombre de variable) pero no le asocia semántica alguna.


== Código <code>const</code> y <code>readonly</code> en C# ==
Se reservó esta palabra clave para desarrollar una extensión del lenguaje Java que incluyese métodos <code>const</code> y punteros a tipos <code>const</code> al estilo C++{{Citation needed|date=February 2011}}. Existe una petición para implementar correctitud de constantes en [[Java Community Process|el Proceso de la Comunidad Java]], pero fue cerrada en 2005 indicando que es imposible implementar dicho cambio de manera que sea retrocompatible.<ref>[http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4211070 Ticket for adding const parameters in Java]</ref>


En [[C_Sharp|lenguaje C#]], el calificador <code>readonly</code> tiene el mismo efecto sobre campos que el calificador <code>final</code> de Java y el calificador <code>const</code> de C++; el calificador <code>const</code> en C# tiene un efecto similar al <code>#define</code> de C++. El efecto inhibidor de herencia que Java aplica con el calificador <code>final</code> sobre métodos es equivalente al calificador <code>sealed</code> de C#.
==Código <code>const</code> y <code>readonly</code> en C#==
En C#, el calificador <code>readonly</code> tiene el mismo efecto sobre campos que el calificador <code>final</code> de Java y el calificador <code>const</code> de C++; el calificador <code>const</code> en C# tiene un efecto similar al <code>#define</code> de C++ (El efecto inhibidor de herencia que Java aplica con el calificador <code>final</code> sobre métodos, es equivalente al calificador <code>sealed</code> de C#).


El contrario que C++, C# no permite calificar métodos y parámetros con <code>const</code>. Sin embargo también se pueden utilizar parámetros de sólo lectura, el [[.NET Framework]] proporciona soporte para convertir colecciones modificables en colecciones inmutables que pueden ser pasadas a métodos y funciones.
El contrario que C++, C# no permite calificar métodos y parámetros con <code>const</code>. Sin embargo, también se pueden utilizar parámetros de sólo lectura; el [[.NET Framework]] proporciona soporte para convertir colecciones modificables en colecciones inmutables que pueden ser pasadas a métodos y funciones.


==Referencias==
== Referencias ==
{{listaref}}
{{listaref}}


[[Categoría:Variables (programación)]]
{{DEFAULTSORT:Const-Correctness}}
[[Categoría:Lenguaje de programación C]]
[[Category:C programming language family]]
[[Category:Data types]]
[[Categoría:Tipos de datos]]
[[Categoría:Artículos con códigos de ejemplo]]
[[Category:Articles with example C code]]
[[Category:Articles with example C++ code]]
[[Category:Programming language topics]]

Revisión del 09:56 23 ago 2013

En programación, la correctitud de constantes (en inglés: const correctness) es el tipo de correctitud que hace referencia a la adecuada declaración de variables u objetos como inmutables. El término es mayormente usado en el contexto de C o C++, y recibe su nombre de la palabra reservada const de estos lenguajes.

El uso de la palabra reservada const indica lo que el programador «debe» hacer, no necesariamente lo que el programador «puede» hacer, pues calificar datos con esta palabra reservada no provoca que se almacenen en un tipo de memoria de sólo lectura, sino que ordena al compilador realizar verificaciones sobre el código en tiempo de compilación para finalizar con un error el proceso de compilación en el caso de intentar modificar un dato constante.

El hecho de que sea posible modificar datos calificados con const en tiempo de ejecución prueba que estos no se almacenan en memoria de sólo lectura. Para realizar estos cambios en tiempo de ejecución, deben evitarse las verificaciones que el compilador realiza sobre los valores const mediante el uso de conversiones de tipo o uniones.

const int constante = 0; // Valor constante

// Referencia no constante a un valor constante, sin usar la conversion const_cast seria un error
int &noConstante = const_cast<int &>(constante);
// Mediante la referencia no constante se modifica el valor constante:
noConstante = 1;

// Puntero a no constante apuntando a un valor constante, sin usar la conversion const_cast seria un error
int *punteroANoConstante = const_cast<int *>(&constante);
// Mediante el puntero a no constante se modifica el valor constante:
*punteroA_No_Constante = 2;

// Puntero a no constante apuntando a un valor constante, sin usar la conversion estilo C seria un error
punteroANoConstante = (int *)&constante;
// Mediante el puntero a no constante se modifica el valor constante:
*punteroA_No_Constante = 3;

// Union que contiene puntero a constante y puntero a NO constante
union constanteYMutable
{
    const int *punteroAConstante;
    int *punteroA_No_Constante;
} u;

// Correcto: Puntero a constante apuntando a un valor constante
u.punteroAConstante = &constante;
// Mediante el otro miembro de la union se modifica el valor constante:
*u.punteroA_No_Constante = 4;

Cabe destacar que el compilador puede decidir realizar optimizaciones sobre los valores calificados con const —como, por ejemplo, utilizar el valor literal en lugar del valor almacenado en la variable—. Esta optimización es conocida como propagación de constantes y también se aplica sobre los literales de texto const char *; dado que no es posible modificar el valor de un literal, el ejemplo anterior daría lugar a un comportamiento no definido.

Los métodos no estáticos pueden declararse como const. Al hacerlo el puntero this dentro del método es de tipo valor_de_retorno const * const en lugar de valor_de_retorno * const. Esto significa que, dentro del método constante, el compilador tratará como error cualquier llamada a otros métodos no constantes o la modificación de cualquier campo del objeto.

En C++, un campo puede ser declarado como mutable, indicando que la anterior restricción no se aplica sobre él. En algunos casos, esto puede ser útil, por ejemplo, para cachear de datos, contar referencias o sincronizar datos. En estos casos, no se altera el estado lógico del objeto, pero no es físicamente constante porque su representación binaria puede cambiar.

Sintaxis de C y lenguajes derivados

En C y otros lenguajes derivados, todos los tipos de datos —incluyendo los definidos por el usuario— pueden ser declarados con const. Una correctitud de constantes dicta que todas las variables u objetos deben ser declarados constantes a no ser que exista intención de modificarlos. Este uso pro-activo de const hace que los valores sean «más fáciles de entender, rastrear y razonar sobre ellos»,[1]​ además de mejorar la legibilidad y comprensión del código, facilitando las tareas del equipo encargado del mantenimiento del código, ya que el propio código muestra la intención de uso de cada valor.

Tipos de datos simples

Para tipos de datos simples —excepto punteros—, el calificador const puede situarse en ambos lados del tipo —es decir, const char foo = 'a'; equivale a char const foo = 'a';—. En algunos compiladores, usar const en ambos lados del tipo —por ejemplo, const char const— genera una advertencia pero no un error.

La flexibilidad para situar el calificador const existe por razones históricas[2]​ —el C y C++ previos a la estandarización imponían pocas, o ninguna regla de ordenación en los calificadores de tipo—. Dado que usar el calificador antes o después del tipo no provoca ambigüedades, se decidió no desarrollar una regla de ordenación para este caso.

Punteros y referencias

El significado de const es más complicado para punteros y referencias —tanto el puntero como el dato apuntado, incluso ambos, pueden ser calificados con const—. Además, la sintaxis puede ser confusa. Pueden declararse punteros constantes que apunten a datos no constantes, o punteros modificables que apunten a datos no modificables, o punteros constantes a valores constantes. No se puede cambiar el dato al que apunta un puntero constante, pero puede modificarse el dato apuntado. Por el contrario, puede modificarse el dato al que apunta un puntero constante —que debe ser un dato del mismo tipo o un tipo convertible al del puntero—, pero no puede modificarse el valor apuntado a través del mismo. Finalmente, puede declararse un puntero constante a un dato constante, pero no puede cambiarse el dato al que apunta ni modificar el valor del dato apuntado a través del mismo. El siguiente ejemplo ilustra estos matices:

void Foo( int * puntero,
          int const * punteroAConst,
          int * const punteroConstante,
          int const * const punteroConstanteAConst )
{
    *puntero = 0;   // Correcto: modifica el dato apuntado
    puntero = NULL; // Correcto: modifica el puntero

    *punteroAConst = 0;   // Error! No puede modificarse el objeto apuntado
    punteroAConst = NULL; // Correcto: modifica el puntero

    *punteroConstante = 0;    // Correcto: modifica el dato apuntado
    punteroConstante  = NULL; // Error! No puede modificarse el puntero

    *punteroConstanteAConst = 0;    // Error! No puede modificarse el objeto apuntado
    punteroConstanteAConst  = NULL; // Error! No puede modificarse el puntero
}

Según las convenciones de C para declaraciones, la declaración sigue al uso, por lo que escribir * sobre un puntero indica su desreferenciación. Así pues, en la declaración int *puntero, al desreferenciar el puntero *puntero se obtiene int, mientras que puntero es un puntero a int. En consecuencia, const modifica el identificador a su derecha.

Sin embargo, las convenciones de C++ asocian el * con el tipo, como en int* puntero y const modifica el tipo a la izquierda. int const * punteroAConstante puede leerse como *punteroAConstante es un int const —el valor es constante—, o punteroAConstante es un int const * —el puntero apunta a un entero constante—:

int *puntero;  // *puntero es un valor int
int const *punteroAConst;     // *punteroAConst es constante
int * const punteroConstante; // punteroConstante es constante

int const * const punteroConstanteAConst; // punteroConstanteAConst es constante (puntero)
                                          // y *punteroConstanteAConst es constante (valor)

Según la convención de C++ de analizar el tipo, no el valor, la regla general es leer la declaración de derecha a izquierda. Así pues, todo lo que quede a la izquierda del asterisco pertenece al tipo apuntado y aquello a la derecha del mismo pertenece a las propiedades del puntero. En el anterior ejemplo, int const * puede ser interpretado como un puntero de sólo lectura que apunta a un entero de lectura-escritura.

También está permitido situar const a la izquierda del tipo en C y C++, como en la siguiente sintaxis:

const int*       punteroAConst;          // identico a: int const *       punteroAConst
const int* const punteroConstanteAConst; // identico a: int const * const punteroConstanteAConst

Esta notación separa con mayor claridad las dos localizaciones de const, y permite que el * siempre se asocie a su tipo precedente, aunque aún necesite ser leído de izquierda a derecha:

int* puntero;
const int* punteroAConst; // (const int)*, no constante (int*)
int* const punteroConstante;
const int* const punteroConstanteAConst;

Las referencias de C++ siguen normas similares. Declarar una referencia const es redundante, ya que no es posible hacer que una referencia apunte a otro objeto:

int i = 22;
int const & referenciaAConst = i;    // Correcto
int & const referenciaConstante = i; // Error, el "const" es redundante

Se pueden obtener declaraciones realmente complicadas usando vectores multidimensionales y referencias —o punteros— a punteros; sin embargo, estos usos se consideran confusos y propensos a errores; en consecuencia, se aconseja evitarlos o reemplazarlos por estructuras de mayor nivel de abstracción.

Parámetros y variables

const puede ser usado tanto en parámetros de función como en variables estáticas o automáticas, incluyendo globales o locales. La interpretación varía entre usos. Una variable const estática —variable global o variable estática local— es una constante, y debe usarse para representar datos como constantes matemáticas —por ejemplo, const double PI = 3.14159— o parámetros generales en tiempo de compilación. Una variable const automática —variable local no estática— debe ser inicializada, aunque puede usarse un valor diferente en cada llamada —por ejemplo, const int x_al_cuadrado = x*x;—. Un parámetro pasado como referencia const indica que el valor referenciado no se modificará, pues forma parte del contrato. Por ello, se suele favorecer el uso de const en parámetros pasados por referencia, pero no en los parámetros pasados por valor.

Métodos

Para aprovechar el diseño por contrato en tipos definidos por el usuario —estructuras y clases—, que pueden tener tanto métodos como campos, el programador debe marcar los métodos de instancia como const si no tiene intención de modificar los campos del objeto. Usar el calificador const sobre los métodos de instancia que lo requieran es esencial para cumplir con la correctitud de constantes, aunque este tipo de correctitud no esté disponible en otros lenguajes de programación orientada a objetos, como Java y C#, en la CLI de Microsoft, o en las Managed Extensions for C++.

Los métodos calificados con const pueden ser llamados desde objetos constantes y no constantes indistintamente, mientras que los métodos sin calificación const sólo pueden llamarse desde objetos no constantes.

El calificador const aplicado sobre un método de instancia afecta al objeto apuntado por el puntero this, que es un argumento implícito pasado a todos los métodos de instancia. Por lo tanto, los métodos constantes son la manera de aplicar la correctitud de constantes al parámetro implícito this. Por ejemplo:

class C
{
    int i;
public:
    int Get() const // Calificado con "const"
      { return i; }
    void Set(int j) // Sin calificacion "const"
      { i = j; }
};

void Foo(C& noConstante, const C& constante)
{
    int y = noConstante.Get(); // Correcto
    int x = constante.Get();   // Correcto: Get() es const

    noConstante.Set(10);  // Correcto: noConstante es modificable
    constante.Set(10);    // Error! Set() no es const mientras que constante es un objecto const
}

En el ejemplo anterior, el puntero implícito this del método Set() tiene el tipo C *const; mientras que el this de Get() es de tipo const C *const, lo cual indica que ese método no puede modificar el objeto mediante el puntero this.

Es posible declarar un método con el mismo nombre pero con diferente calificación const —y, posiblemente, diferente uso— para adaptarse a ambos tipos de llamadas. Por ejemplo:

class MiVector
{
    int data[100];
public:
    int &       Get(int i)       { return data[i]; }
    int const & Get(int i) const { return data[i]; }
};

void Foo( MiVector & vectorNoConstante, MiVector const & vectorConstante )
{
    // Obtenemos una referencia a un elemento del vector
    // y modificamos su valor:

    vectorNoConstante.Get( 5 ) = 42; // Correcto! (llama int & MiVector::Get(int))
    vectorConstante.Get( 5 )   = 42; // Error! (llama int const & MiVector::Get(int) const)
}

La calificación const del objeto determina qué versión de MiVector::Get() será llamada, y si se obtendrá una referencia que pueda ser modificada o de sólo lectura.

Técnicamente, ambos métodos tienen diferentes firmas, ya que sus punteros this tienen diferentes tipos. Esto permite al compilador escoger el método correcto.

Excepciones a la correctitud de constantes

Existen varias excepciones a la correctitud de constantes puras en C y C++, principalmente, por retrocompatibilidad de código.

La primera excepción, tan sólo aplicable a C++, es el uso de const_cast, que permite eliminar la calificación const de un dato, haciéndolo modificable. La necesidad de eliminar la calificación const surge del uso de código heredado, o de bibliotecas que no pueden cambiarse pero que no cumplen con la correctitud de constantes. Por ejemplo:

// Prototipo de una funcion que no podemos modificar pero que
// sabemos que no modifica los datos apuntados por el puntero.
void LibraryFunc(int *ptr, int size);

void CallLibraryFunc(int const *ptr, int size)
{
    LibraryFunc(ptr, size); // Error! ptr es constante

    int *nonConstPtr = const_cast<int*>(ptr); // se elimina la calificacion const
    LibraryFunc(nonConstPtr, size);  // Correcto
}

Sin embargo, el estándar ISO de C++ indica que intentar modificar un objeto calificado con const a través de const_cast desemboca en un comportamiento no definido. En el ejemplo anterior, si el puntero ptr apunta a una variable global, local, o campo calificado con const, o a un objeto creado dinámicamente mediante new const int, el código sólo será correcto si la función LibraryFunc realmente no modifica el valor apuntado por ptr.

Existe una excepción[cita requerida] que se aplica tanto a C como a C++: los campos puntero o referencia «ignoran» la calificación const de sus propietarios —es decir, en un objeto constante, todos sus miembros son constantes, excepto en el caso de punteros y referencias—. Considérese el siguiente código:

struct estructura
{ 
    int valor;
    int *puntero;
};

void Foo(const estructura & e)
{
    int i  = 42;
    e.valor  = i;    // Error: e es constante, ergo valor es un entero constante
    e.puntero  = &i; // Error: e es constante, ergo puntero es un puntero constante a un entero
    *e.puntero = i;  // Correcto: el dato apuntado por puntero no es constante,
                     // aunque esto a veces no sea deseable
}

Aunque el objeto e pasado a Foo() sea constante, lo que implica que todos sus campos sean constantes, el dato apuntado por e.puntero puede ser modificado pese a que no es adecuado desde el punto de vista de la correctitud de constantes —podría darse el caso de que e fuese propietario del dato apuntado—.

Por este motivo, se ha argumentado que la calificación por defecto para punteros y referencias miembro debe ser una correctitud de constantes más profunda, pudiendo modificar el comportamiento por defecto con el calificador mutable cuando el dato apuntado no pertenezca al objeto contenedor, pero aplicar este cambio crearía problemas de compatibilidad con código existente. Aun así, esta excepción permanece en C y C++.

La anterior excepción puede ser corregida usando una clase que oculte el puntero tras una interfaz que cumpla con la correctitud de constantes; no obstante, estas clases tampoco soportan las semánticas habituales de copia desde un objeto calificado con const —lo que implica que la clase contenedora tampoco puede ser copiada con las semánticas de copia habituales—, ni permiten que otras excepciones ignoren la correctitud de constantes mediante copia involuntaria o intencionada.

Finalmente, varias funciones en la biblioteca estándar de C no cumplen con la correctitud de constantes, dado que reciben punteros const a cadenas de caracteres y devuelven un puntero no-const a una parte de la misma cadena. Algunas implementaciones de la biblioteca C++ estándar, como la de Microsoft,[3]​ intentan corregir estas excepciones proporcionando dos versiones de algunas funciones: una versión con const, y otra sin ello.

Correctitud de datos volátiles

El cualificador volatile de C y C++ indica que un objeto puede ser cambiado externamente en cualquier momento por medios ajenos al programa y, por lo tanto, debe volverse a leer de la memoria cuando se accede a él. Este calificador se suele encontrar en código que manipula hardware directamente —en sistemas embebidos y controladores de dispositivos— y en aplicaciones multihilo.

volatile se puede usar de la misma manera que const en declaraciones de variables, punteros, referencias y métodos. De hecho, volatile es usado en ocasiones para implementar estrategias de diseño por contrato que Andrei Alexandrescu denomina «correctitud de volátiles»,[4]​ aunque es mucho menos común que la correctitud de constantes. El calificador volatile también puede ser eliminado mediante const_cast, y puede combinarse con el calificador const como puede verse en este ejemplo:

// Configura una referencia a un registro de hardware de solo lectura
// que está situado en una posicion de memoria determinada.
const volatile int & registroDeHardware = *reinterpret_cast<int*>(0x8000);

int valorActual = registroDeHardware; // Leemos la memoria
int nuevoValor = registroDeHardware;  // Leemos de nuevo

registroDeHardware = 5; // Error! No se puede escribir sobre una variable const

Dado que registroDeHardware es volatile, aunque el programador no pueda modificar su valor, no existen garantías de que mantenga los mismos datos en lecturas sucesivas. Las semánticas de esta variable indican que es de sólo lectura pero no inmutable.

const e immutable en D

En la segunda versión del lenguaje D, exiten dos palabras reservadas relacionadas con la correctitud de constantes:[5]

  • la palabra reservada immutable denota un dato que no puede ser modificado a través de ninguna referencia;
  • la palabra reservada const se refiere a una referencia inmutable de un dato mutable.

Al contrario que el const de C++, const e immutable de D son transitivos, y cualquier dato alcanzable a través de const o immutable es const o immutable, respectivamente.

Ejemplo de const e immutable en D

int[] foo = new int[5];  // foo es mutable.
const int[] bar = foo;   // bar apunta a foo de manera inmutable.
immutable int[] baz = foo;  // Error: las referencias a datos inmutables deben ser inmutables.

immutable int[] nums = new immutable(int)[5];  // No se pueden crear referencias mutables a nums
const int[] constNums = nums;  // Correcto. immutable se puede convertir implicitamente a const.
int[] mutableNums = nums;  // Error: No se puede crear una referencia mutable a un dato inmutable.

Ejemplo de const transitivo en D

class Foo {
    Foo next;
    int num;
}

immutable Foo foo = new immutable(Foo);
foo.next.num = 5;  // Error.  foo.next es del tipo immutable(Foo).
                   // foo.next.num es del tipo immutable(int).

final en Java

En Java, el calificador final indica que el campo o variable no es asignable. Por ejemplo:

final int i = 3;
i = 4; // Error! No se puede modificar un objeto "final"

El compilador debe ser capaz de decidir dónde inicializar las variables calificadas con final, y debe ser inicializada una única vez o la clase no debe compilar. El final de Java y el const de C++ tienen el mismo significado al aplicarse sobre tipos básicos.

const int i = 3; // declaracion C++ 
i = 4; // Error!

Respecto a los punteros, una referencia final en Java tiene un significado similar a un puntero constante en C++.

Foo *const bar = direccion_de_memoria; // puntero constante

En el ejemplo anterior, bar debe ser inicializado en el momento de la declaración y no puede cambiar su valor en adelante, pero el valor al que apunta sí es modificable. Es decir, *bar = valor es válido, solo que no puede cambiar el lugar al que apunta. Las referencias finales de Java funcionan de la misma manera, con la particularidad de que no es necesario inicializarlas.

final Foo i; // una declaracion Java

Nótese que Java no usa punteros.[6]

También se puede declarar un puntero a datos de sólo lectura en C++.

const Foo *bar;

En el ejemplo anterior, bar puede modificarse para que apunte a cualquier dato de tipo Foo; sin embargo el valor apuntado no podrá ser modificado mediante el puntero bar. No existe un mecanismo equivalente en Java. Así pues, tampoco existen métodos const.

No se puede forzar la correctitud de constantes en Java. Sin embargo, definiendo interfaces de sólo lectura a una clase se garantiza que los objetos puedan ser utilizados sin posibilida de modificarlos. El framework de colecciones de Java proporciona mecanismos para crear un adapador sobre una Collection mediante Collections.unmodifiableCollection() y métodos similares.

Los métodos en Java pueden ser declarados como final, pero este tipo de declaración no tiene relación con la correctitud de constantes sino con la herencia —significa que el método no puede ser sobreescrito en clases derivadas—.

El lenguaje Java marca const como palabra reservada —por lo que no puede usarse como nombre de variable—, pero no le asocia semántica alguna. Esta palabra clave fue reservada para desarrollar una extensión del lenguaje Java que incluyese métodos const y punteros a tipos const al estilo C++[cita requerida]. Existe una petición para implementar correctitud de constantes en el Proceso de la Comunidad Java, pero fue cerrada en 2005 indicando que es imposible implementar dicho cambio de manera que sea retrocompatible.[7]

Código const y readonly en C#

En lenguaje C#, el calificador readonly tiene el mismo efecto sobre campos que el calificador final de Java y el calificador const de C++; el calificador const en C# tiene un efecto similar al #define de C++. El efecto inhibidor de herencia que Java aplica con el calificador final sobre métodos es equivalente al calificador sealed de C#.

El contrario que C++, C# no permite calificar métodos y parámetros con const. Sin embargo, también se pueden utilizar parámetros de sólo lectura; el .NET Framework proporciona soporte para convertir colecciones modificables en colecciones inmutables que pueden ser pasadas a métodos y funciones.

Referencias