c++ - define - typedef struct en c
¿Por qué la corrección de const es específica de C++? (14)
Anders Hejlsberg (arquitecto de C #): ... Si declaras un método que toma un Bla no const, no puedes pasarle un const Bla. Entonces ahora estás atrapado. Entonces, gradualmente necesitas una versión constante de todo lo que no es const, y terminas con un mundo en la sombra.
Entonces otra vez: si comenzaste a usar "const" para algunos métodos usualmente forzaste a usar esto en la mayoría de tu código. Pero el tiempo dedicado a mantener (tipear, recompilar cuando falta algo, etc.) de corrección de constidad en el código parece mayor que para la fijación de posibles (muy raros) problemas causados por no usar en absoluto la corrección de const. Por lo tanto, la falta de compatibilidad con corrección de versiones en los lenguajes modernos (como Java, C #, Go, etc.) puede dar como resultado un tiempo de desarrollo ligeramente reducido para la misma calidad de código.
Un ticket de solicitud de mejora para implementar const correctness existía en el Proceso de comunidad de Java desde 1999, pero se cerró en 2005 debido a la "contaminación const" mencionada anteriormente y también razones de compatibilidad: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4211070
Aunque el lenguaje C # no tiene una construcción const correctness, posiblemente aparecerá una funcionalidad similar en "Microsoft Code Contracts" (biblioteca + herramientas de análisis estáticas) para .NET Framework utilizando atributos [Pure] e [Immutable]: Funciones Puras en C #
Descargo de responsabilidad: soy consciente de que hay dos preguntas sobre la utilidad de la corrección de const, sin embargo, ninguna discutió cómo la corrección de const es necesaria en C ++ en comparación con otros lenguajes de programación. Además, no estoy satisfecho con las respuestas proporcionadas a estas preguntas.
He usado algunos lenguajes de programación ahora, y una cosa que me molesta en C ++ es la noción de const-correctness. No existe tal noción en Java, C #, Python, Ruby, Visual Basic, etc., esto parece ser muy específico de C ++.
Antes de referirme a C ++ FAQ Lite, lo he leído y no me convence. Los programas perfectamente válidos y confiables se escriben en Python todo el tiempo, y no hay palabras clave const o equivalentes. En Java y C #, los objetos pueden declararse finales (o const), pero no hay funciones de miembro de const ni parámetros de función const. Si una función no necesita modificar un objeto, puede tomar una interfaz que solo proporcione acceso de lectura al objeto. Esa técnica también se puede usar en C ++. En los dos sistemas C ++ del mundo real en los que he trabajado, había muy poco uso de const en cualquier lugar, y todo funcionó bien. Así que estoy lejos de estar convencido de la utilidad de dejar que const contamine una base de código.
Me pregunto qué hay en C ++ que haga que const sea necesario, a diferencia de otros lenguajes de programación.
Hasta ahora, solo he visto un caso en el que se debe usar const:
#include <iostream>
struct Vector2 {
int X;
int Y;
};
void display(/* const */ Vector2& vect) {
std::cout << vect.X << " " << vect.Y << std::endl;
}
int main() {
display(Vector2());
}
Compilando esto con const comentado es aceptado por Visual Studio, pero con la advertencia C4239 , se usa una extensión no estándar. Por lo tanto, si desea la brevedad sintáctica de pasar temporarios, evitar copias y mantenerse conforme a las normas, debe pasar por la referencia constante, no hay forma de evitarlo. Aún así, esto es más como un capricho que una razón fundamental.
De lo contrario, realmente no hay ninguna situación en la que se deba usar const, excepto cuando se interactúa con otro código que usa const. Const me parece poco más que una plaga autojustificada que se extiende a todo lo que toca:
La razón por la que const funciona en C ++ es porque puedes descartarla. Si no pudieras descartarlo, entonces tu mundo sería horrible. Si declaras un método que toma una const Bla, podrías pasarle una Bla no const. Pero si es al revés, no puedes. Si declaras un método que toma un Bla no const, no puedes pasarle un const Bla. Entonces ahora estás atrapado. Entonces, gradualmente necesitas una versión constante de todo lo que no es const, y terminas con un mundo en la sombra. En C ++ te salgas con la tuya, porque al igual que con cualquier cosa en C ++, es puramente opcional si deseas esta verificación o no. Puedes golpear la constness si no te gusta.
Anders Hejlsberg (arquitecto C #), CLR Design Choices
Bueno, me habrá llevado 6 años entender realmente, pero ahora finalmente puedo responder mi propia pregunta.
La razón por la que C ++ tiene "const-correctness" y que Java, C #, etc. no lo hacen, es que C ++ solo admite tipos de valores , y estos otros lenguajes solo admiten o al menos predeterminan los tipos de referencia .
Veamos cómo C #, un lenguaje que predetermina los tipos de referencia, se ocupa de la inmutabilidad cuando se trata de tipos de valores. Supongamos que tiene un tipo de valor variable y otro tipo que tiene un campo de solo lectura de ese tipo:
struct Vector {
public int X { get; private set; }
public int Y { get; private set; }
public void Add(int x, int y) {
X += x;
Y += y;
}
}
class Foo {
readonly Vector _v;
public void Add(int x, int y) => _v.Add(x, y);
public override string ToString() => $"{_v.X} {_v.Y}";
}
void Main()
{
var f = new Foo();
f.Add(3, 4);
Console.WriteLine(f);
}
¿Qué debería hacer este código?
- no compilar
- imprimir "3, 4"
- imprimir "0, 0"
La respuesta es # 3. C # intenta respetar su palabra clave "readonly" invocando el método Add on a throw away del objeto. Eso es raro, sí, pero ¿qué otras opciones tiene? Si invoca el método en el Vector original, el objeto cambiará, violando el valor "readonly" -ness del campo. Si no se puede compilar, los miembros de tipo de valor de solo lectura son bastante inútiles, porque no puede invocar ningún método sobre ellos, por temor a que puedan cambiar el objeto.
Si solo pudiéramos etiquetar qué métodos son seguros para invocar instancias de solo lectura ... ¡Espera, eso es exactamente lo que son los métodos const en C ++!
C # no se molesta con los métodos const porque no usamos tipos de valores tanto en C #; simplemente evitamos tipos de valores mutables (y los declaramos "malvados", vea 1 , 2 ).
Además, los tipos de referencia no adolecen de este problema, ya que cuando marca una variable de tipo de referencia como de solo lectura, lo que es de solo lectura es la referencia, no el objeto en sí . Es muy fácil de aplicar por el compilador, puede marcar cualquier asignación como un error de compilación excepto en la inicialización. Si todo lo que usa son tipos de referencia y todos sus campos y variables son de solo lectura, obtendrá inmutabilidad en todas partes a un bajo costo sintáctico. F # funciona del todo así. Java evita el problema simplemente no admite tipos de valores definidos por el usuario.
C ++ no tiene el concepto de "tipos de referencia", solo "tipos de valores" (en C # -lingo); algunos de estos tipos de valores pueden ser punteros o referencias, pero al igual que los tipos de valores en C #, ninguno de ellos posee su almacenamiento . Si C ++ trató "const" en sus tipos de la forma en que C # trata "de solo lectura" en los tipos de valores, sería muy confuso como demuestra el ejemplo anterior, sin importar la desagradable interacción con los constructores de copia.
Entonces C ++ no crea una copia descartable, porque eso crearía un dolor interminable. Tampoco te prohíbe llamar a ningún método sobre los miembros, porque, bueno, el lenguaje no sería muy útil. Pero todavía quiere tener alguna noción de "readonly" o "const-ness".
C ++ intenta encontrar un camino intermedio al hacerle etiquetar qué métodos son seguros para llamar a los miembros de la estafa, y luego confía en que haya sido fiel y preciso en su etiquetado y llame directamente a los métodos sobre los objetos originales. Esto no es perfecto, es detallado, y se te permite violar la constidad tanto como quieras, pero es posiblemente mejor que todas las otras opciones.
En C, Java y C # se puede ver mirando el sitio de llamadas si un objeto pasado puede ser modificado por una función:
- en Java, sabes que definitivamente puede ser.
- en C, usted sabe que solo puede ser si hay un ''&'', o equivalente.
- en c # necesita decir ''ref'' en el sitio de llamadas también.
En C ++ en general, no se puede decir esto, ya que una llamada de referencia no const se ve idéntica a la de pasar por valor. Tener referencias de referencia le permite configurar y aplicar la convención de C.
Esto puede hacer una gran diferencia en la legibilidad de cualquier código que llame funciones. Que probablemente sea suficiente para justificar una función de idioma.
En realidad, no es ... no del todo, de todos modos.
En otros idiomas, especialmente los idiomas funcionales o híbridos, como Haskell, D, Rust y Scala, tiene el concepto de mutabilidad: las variables pueden ser mutables o inmutables, y por lo general son inmutables por defecto.
Esto le permite a usted (y a su compilador / intérprete) razonar mejor sobre las funciones: si sabe que una función solo toma argumentos inmutables, entonces sabe que esa función no es la que está mutando su variable y causando un error.
C y C ++ hacen algo similar usando const, excepto que es una garantía mucho menos firme: la inmutabilidad no se aplica; una función más allá de la pila de llamadas podría descartar la constness y mutar sus datos, pero eso sería una violación deliberada del contrato de la API. Entonces, la intención o la mejor práctica es que funcione como la inmutabilidad en otros idiomas.
Dicho todo esto, C ++ 11 ahora tiene una palabra clave mutable real, junto con la palabra clave const más limitada.
Esta charla y video de Herb Sutter explica las nuevas connotaciones de const
con respecto a la seguridad de hilos.
Es posible que Constness no haya sido algo de lo que haya tenido que preocuparse demasiado, pero con C ++ 11, si desea escribir un código seguro para subprocesos, debe comprender la importancia de const
y mutable
La corrección de Const proporciona dos ventajas notables a C ++ que puedo pensar, una de las cuales lo hace bastante único.
- Permite nociones omnipresentes de datos mutables / inmutables sin requerir un conjunto de interfaces. Los métodos individuales pueden ser anotados en cuanto a si pueden ejecutarse o no en objetos const, y el compilador hace cumplir esto. Sí, a veces puede ser una molestia, pero si lo usa de manera consistente y no usa
const_cast
, tiene seguridad comprobada por el compilador con respecto a los datos mutables frente a los inmutables. - Si un objeto o elemento de datos es
const
, el compilador puede colocarlo en la memoria de solo lectura. Esto puede ser particularmente importante en sistemas integrados. C ++ apoya esto; algunos otros idiomas lo hacen. Esto también significa que, en el caso general, no se puede transmitirconst
lejos, aunque en la práctica puede hacerlo en la mayoría de los entornos.
C ++ no es el único lenguaje con const correctness o algo así. OCaml y Standard ML tienen un concepto similar con terminología diferente: casi todos los datos son inmutables (const), y cuando quiere que algo se pueda mutar, utiliza un tipo diferente (un tipo de ref
) para lograr eso. Por lo tanto, es exclusivo de C ++ en sus idiomas vecinos.
Finalmente, viene la otra dirección: ha habido ocasiones en que he querido const en Java. final
veces no llega lo suficientemente lejos como para crear datos completamente inmutables (especialmente vistas inmutables de datos mutables) y no desea crear interfaces. Observe el soporte de colección no modificable en la API de Java y el hecho de que solo verifica en tiempo de ejecución si la modificación está permitida para un ejemplo de por qué const es útil (o al menos la estructura de la interfaz debe profundizarse para tener List y MutableList) - allí no hay razón para que intentar mutar una estructura inmutable no pueda ser un error de compilación.
La palabra clave const en C ++ (tal como se aplica a los parámetros y las declaraciones de tipo) es un intento de evitar que los programadores disparen su dedo gordo del pie y tomen su pierna entera en el proceso.
La idea básica es etiquetar algo como "no se puede modificar". Un tipo const no se puede modificar (por defecto). Un puntero de const no puede apuntar a una nueva ubicación en la memoria. Simple, ¿verdad?
Bueno, ahí es donde entra en juego la precisión. Estas son algunas de las combinaciones posibles en las que puede encontrarse cuando usa const:
Una variable const implica que los datos etiquetados por el nombre de la variable no se pueden modificar.
Un puntero a una variable const implica que el puntero puede modificarse, pero los datos en sí no pueden.
Un puntero const a una variable implica que el puntero no se puede modificar (para apuntar a una nueva ubicación de memoria), pero que los datos a los que se pueden modificar los punteros pueden modificarse.
Un puntero const para una variable const implica que ni el puntero ni los datos a los que apunta se pueden modificar.
¿Ves cómo algunas cosas pueden ser tontas allí? Es por eso que cuando usas const, es importante estar en lo correcto en lo que estás etiquetando.
El punto es que esto es solo un truco en tiempo de compilación. El etiquetado simplemente le dice al compilador cómo interpretar las instrucciones. Si te alejas de const, puedes hacer lo que quieras. Pero igual tendrá que llamar a métodos que tienen requisitos de const con tipos que se emiten adecuadamente.
La programación consiste en escribir en un idioma que la computadora procesará en última instancia, pero que es tanto una forma de comunicarse con la computadora como con otros programadores en el mismo proyecto. Cuando usa un idioma, está restringido a los conceptos que se pueden expresar en él, y const es solo un concepto más que puede usar para describir su problema y su solución.
La constancia le permite expresar claramente desde el tablero de diseño hasta el concepto de código uno que otros lenguajes carecen. Como vienes de un idioma que no lo tiene, puedes parecer desconcertado por un concepto que nunca has usado, si nunca lo has usado antes, ¿qué tan importante puede ser?
El lenguaje y el pensamiento están estrechamente relacionados. Solo puede expresar sus pensamientos en el idioma que habla, pero el lenguaje también cambia su forma de pensar. El hecho de que no tenga la palabra clave const en los idiomas con los que trabajó implica que ya ha encontrado otras soluciones para los mismos problemas, y esas soluciones son lo que le parece natural.
En la pregunta, usted argumentó que puede proporcionar una interfaz no mutante que puede ser utilizada por funciones que no necesitan cambiar el contenido de los objetos. Si lo piensas por un segundo, esta misma oración te dice por qué const es un concepto con el que quieres trabajar. Tener que definir una interfaz no mutante e implementarla en su clase es una solución al hecho de que no puede expresar ese concepto en su idioma.
La constancia le permite expresar esos conceptos en un lenguaje que el compilador (y otros programadores) puedan entender. Está estableciendo un compromiso sobre lo que hará con los parámetros que recibe, las referencias que almacena o la definición de límites sobre lo que los usuarios de su clase pueden hacer con las referencias que proporciona. Prácticamente cada clase no trivial puede tener un estado representado por atributos, y en muchos casos hay invariantes que deben mantenerse. El lenguaje le permite definir funciones que ofrecen acceso a algunos datos internos mientras que al mismo tiempo limita el acceso a una vista de solo lectura que garantiza que ningún código externo romperá sus invariantes.
Este es el concepto que extraño más cuando me desplazo a otros idiomas. Considere un escenario donde tiene una clase C que tiene entre otros un atributo A de tipo que debe ser visible para el código externo (los usuarios de su clase deben poder consultar cierta información sobre a). Si el tipo de A tiene alguna operación de mutación, entonces para evitar que el código de usuario cambie su estado interno, debe crear una copia de a y devolverla. El programador de la clase debe saber que se debe realizar una copia y debe realizar la copia (posiblemente costosa). Por otro lado, si pudieras expresar la constancia en el lenguaje, podrías simplemente devolver una referencia constante al objeto (en realidad, una referencia a una vista constante del objeto) y simplemente devolver el elemento interno. Esto permitirá que el código de usuario llame a cualquier método del objeto que se comprueba como no mutante, preservando así los invariantes de su clase.
El problema / ventaja, todo depende del punto de vista, de la constancia es que es viral. Cuando ofrece una referencia constante a un objeto, solo se pueden invocar aquellos métodos marcados como no mutante, y debe decirle al compilador cuál de los métodos tiene esta propiedad. Cuando declara que un método es constante, le está diciendo al compilador que el código de usuario que llama a ese método mantendrá el estado del objeto. Cuando define (implementa) un método que tiene una firma constante, el compilador le recordará su promesa y realmente le solicitará que no modifique internamente los datos.
El lenguaje le permite contar las propiedades del compilador de sus métodos que no puede expresar de otra manera, y al mismo tiempo, el compilador le dirá cuando no está cumpliendo con su diseño y tratará de modificar los datos.
En este contexto, nunca se debe usar const_cast <>, ya que los resultados pueden llevarlo al ámbito del comportamiento indefinido (tanto desde el punto de vista del lenguaje: el objeto podría estar en la memoria de solo lectura, como desde el punto de vista del programa : puede romper invariantes en otras clases). Pero eso, por supuesto, ya sabes si lees las preguntas frecuentes de C ++ lite.
Como nota al margen, la palabra clave final en Java no tiene realmente nada que ver con la palabra clave const en C ++ cuando se trata de referencias (en referencias de C ++ o punteros). La palabra clave final modifica la variable local a la que hace referencia, ya sea un tipo básico o una referencia, pero no es un modificador del objeto referido. Es decir, puede llamar a los métodos de mutación a través de una referencia final y así proporcionar cambios en el estado del objeto referido. En C ++, las referencias son siempre constantes (solo puede vincularlas a un objeto / variable durante la construcción) y la palabra clave const modifica cómo el código de usuario puede tratar con el objeto referido. (En el caso de los punteros, puede usar la palabra clave const tanto para el dato como para el puntero: X const * const declara un puntero constante a una constante X )
No creo que nadie afirme que la corrección de const es "necesaria". Pero, de nuevo, las clases tampoco son realmente necesarias, ¿verdad? Lo mismo ocurre con los espacios de nombres, excepciones, ... obtienes la imagen.
Const-correctness ayuda a detectar errores en tiempo de compilación, y es por eso que es útil.
Por ejemplo, tienes una función:
void const_print(const char* str)
{
cout << str << endl;
}
Otro método
void print(char* str)
{
cout << str << endl;
}
En principal:
int main(int argc, char **argv)
{
const_print("Hello");
print("Hello"); // syntax error
}
Esto porque "hello" es un puntero const char, la cadena (estilo C) se coloca en la memoria de solo lectura. Pero en general es útil cuando el programador sabe que el valor no se cambiará. Por lo tanto, para obtener un error de compilación en lugar de un error de segmentación. Al igual que en las asignaciones no deseadas:
const int a;
int b;
if(a=b) {;} //for mistake
Como el operando izquierdo es un const int.
Si está escribiendo programas para dispositivos integrados con datos en FLASH o ROM, no podrá vivir sin corrección de const. Le da la capacidad de controlar el manejo correcto de los datos en diferentes tipos de memoria.
También debe usar métodos de const para aprovechar la optimización del valor de retorno. Ver el artículo 20 más eficaz de Scott Meyers C ++.
Tienes razón, la corrección de const no es necesaria. Ciertamente puede escribir todo su código sin la palabra clave const y hacer que las cosas funcionen, tal como lo hace en Java y Python.
Pero si haces eso, ya no obtendrás la ayuda del compilador para verificar si hay violaciones de const. Los errores que el compilador le habría informado en tiempo de compilación ahora se encontrarán solo en tiempo de ejecución, si es que lo hace, y por lo tanto le tomará más tiempo diagnosticar y corregir.
Por lo tanto, tratar de subvertir o evitar la característica de corrección de const es solo hacer las cosas más difíciles para usted a largo plazo.
const es una forma de expresar algo. Sería útil en cualquier idioma, si pensabas que era importante expresarlo. No tienen la función, porque los diseñadores de idiomas no los encontraron útiles. Si la función estuviera allí, sería casi tan útil, creo.
En cierto modo lo considero como especificaciones de lanzamiento en Java. Si te gustan, probablemente te gusten en otros idiomas. Pero los diseñadores de los otros idiomas no pensaron que fuera tan importante.