Norma C++

Corrección de Const

¿Qué es “corrección de const”?

Algo bueno. Significa usar la palabra clave const para evitar que los objetos const se muten.

Por ejemplo, si desea crear una función f() que acepte un std::string, además, desea prometer a los llamantes que no cambien el std::string del llamante que se pasa a f(), puede hacer que f() reciba su parámetro std::string

  • void f1(const std::string& s); // Pase por referencia-a-const
  • void f2(const std::string* sptr); // Pase por puntero a-const
  • void f3(std::string s); // Pasar por valor

En los casos pasar por referencia aconst y pasar por puntero aconst, cualquier intento de cambiar elstd::string de la persona que llama dentro de las funciones f() será marcado por el compilador como un error en tiempo de compilación. Esta comprobación se realiza completamente en tiempo de compilación: no hay espacio de tiempo de ejecución ni coste de velocidad para const. En el caso de valor de paso por (f3()), la función llamada obtiene una copia de std::stringdel llamante. Esto significa que f3() puede cambiar su copia local, pero la copia se destruye cuando f3() regresa. En particular, f3() no puede cambiar el objeto std::stringde la persona que llama.

Como ejemplo opuesto, supongamos que desea crear una función g() que acepte un std::string, pero desea que las personas que llaman sepan que g() puede cambiar el objeto std::string de la persona que llama. En este caso, puede tener g() recibir su parámetrostd::string

  • void g1(std::string& s); // Pase por referencia a no-const
  • void g2(std::string* sptr); // Pase por puntero a no-const

La falta de const en estas funciones le dice al compilador que se les permite (pero no se les requiere) cambiar el objeto std::string de thecaller. Por lo tanto, pueden pasar su std::string a cualquiera de las funciones f(), pero solo f3()(la que recibe su parámetro “por valor”) puede pasar su std::string a g1() o g2(). Si f1()o f2() necesita llamar a cualquiera de las funciones g(), se debe pasar una copia local del objeto std::string a la función g(); el parámetro a f1() o f2() no se puede pasar directamente a ninguna de las funciones g(). Por ejemplo.,

void g1(std::string& s);void f1(const std::string& s){ g1(s); // Compile-time Error since s is const std::string localCopy = s; g1(localCopy); // Okay since localCopy is not const}

Naturalmente, en el caso anterior, cualquier cambio que g1() realice se realizará en el objeto localCopy que sea local a f1(). En particular, no se realizarán cambios en el parámetro const que se pasó por referencia a f1().

¿Cómo se relaciona la “corrección de const” con la seguridad de tipo ordinario?

Declarar la const-ness de un parámetro es solo otra forma de seguridad de tipo.

Si encuentra que la seguridad de tipo ordinario le ayuda a corregir los sistemas (lo hace; especialmente en sistemas grandes), encontrará queconst la corrección también ayuda.

El beneficio de const corrección es que evita que modifique inadvertidamente algo que no esperaba que se modificara. Terminas necesitando decorar tu código con algunas pulsaciones de teclas adicionales (la palabra clave const), con la ventaja de que le estás diciendo al compilador y a otros programadores una pieza adicional de información semántica importante, información que el compilador usa para evitar errores y otros programadores la usan como documentación.

Conceptualmente se puede imaginar que const std::string, por ejemplo, es una clase diferente a la std::string ordinaria, ya que la variante const carece conceptualmente de las diversas operaciones mutativas que están disponibles en la variante noconst. Por ejemplo, puede imaginar conceptualmente que un const std::string simplemente no tiene un operador de asignación+= ni ninguna otra operación mutativa.

¿Debo intentar corregir las cosas “antes” o “después”?

En el muy, muy, muy principio.

Back-parches const corrección resulta en un efecto de bola de nieve: cada const agregar “aquí” requiere de cuatro moreto ser añadido “por allí.”

Agregue const temprano y a menudo.

¿Qué significa “const X* p”?

Significa p apunta a un objeto de clase X, pero p no se puede usar para cambiar ese objeto X (naturalmente p también podría ser NULL).

Léalo de derecha a izquierda: “p es un puntero a una X que es constante.”

Por ejemplo, si la clase X tiene una función miembro const como inspect() const, está bien decirp->inspect(). Pero si la clase X tiene una función miembro que no esconst llamada mutate(), es un error si dice p->mutate().

Significativamente, este error es detectado por el compilador en tiempo de compilación-no se realizan pruebas en tiempo de ejecución. Eso significa que constno ralentiza su programa y no requiere que escriba casos de prueba adicionales para verificar cosas en tiempo de ejecución: el compilador hace el trabajo en tiempo de compilación.

¿Cuál es la diferencia entre “const X* p”, “X* const p” y “const X* const p”?

Lea las declaraciones de puntero de derecha a izquierda.

  • const X* p significa ” p apunta a un X que es const“: el objeto X no se puede cambiar a través dep.
  • X* const p significa ” p es un puntero const a un X que no esconst“: no puede cambiar el puntero pen sí, pero puede cambiar el objeto X a través de p.
  • const X* const p significa ” p es un puntero const a un X que es const“: no puede cambiar el puntero p en sí, ni puede cambiar el objeto X a través de p.

Y, oh sí, ¿mencioné leer sus declaraciones de puntero de derecha a izquierda?

¿Qué significa “const X& x”?

Significa x alias un objeto X, pero no puede cambiar ese objeto X a través de x.

Léalo de derecha a izquierda: “x es una referencia a un X que es const.”

Por ejemplo, si la clase X tiene una función miembro const como inspect() const, está bien decirx.inspect(). Pero si la clase X tiene una función miembro que no esconst llamada mutate(), es un error si dice x.mutate().

Esto es completamente simétrico con punteros a const, incluido el hecho de que el compilador hace todas las comprobaciones en tiempo de compilación, lo que significa que const no ralentiza su programa y no requiere que escriba casos de prueba adicionales para verificar cosas en tiempo de ejecución.

¿Qué significan “X const& x” y “X const* p”?

X const& x equivale a const X& x, y X const* x equivale aconst X* x.

Algunas personas prefieren el estilo const -on-the-right, llamándolo ” consistente const “o, usando un término acuñado por Simon Brand,” Este const.”De hecho, el estilo” East const “puede ser más consistente que la alternativa: el estilo” East const ” siempre coloca el const a la derecha de lo que constituye, mientras que el otro estilo a veces coloca el const a la izquierda y a veces a la derecha (para declaraciones de puntero const y funciones miembro const).

Con el estilo “Este const“, una variable local que es const se define con const a la derecha:int const a = 42;. De manera similar, una variable static que es const se define como static double const x = 3.14;.Básicamente, cada const termina a la derecha de la cosa que constituye, incluida la const que se requiere para estar a la derecha: declaraciones de puntero const y con una función miembro const.

El estilo “East const” también es menos confuso cuando se usa con alias de tipo: ¿Por qué foo y bar tienen diferentes tipos aquí?

using X_ptr = X*;const X_ptr foo;const X* bar;

Usar el estilo” Este const ” lo hace más claro:

using X_ptr = X*;X_ptr const foo;X* const foobar;X const* bar;

Es más claro aquí que foo y foobar son del mismo tipo y que bar es un tipo diferente.

El estilo” East const ” también es más consistente con las declaraciones de puntero. Contraste con el estilo tradicional:

const X** foo;const X* const* bar;const X* const* const baz;

con el estilo “Este const

X const** foo;X const* const* bar;X const* const* const baz;

A pesar de estos beneficios, el estilo correcto constaún no es popular, por lo que el código heredado tiende a tener el estilo tradicional.

¿Tiene sentido “X& const x”?

No, es una tontería.

Para averiguar lo que significa la declaración anterior, léala de derecha a izquierda: “x es una referencia const a X“. Pero eso es redundante: las referencias son siempre const, en el sentido de que nunca se puede volver a colocar una referencia para hacer que se refiera a un objeto diferente. Nunca. Con o sin const.

En otras palabras, “X& const x” es funcionalmente equivalente a “X& x“. Dado que no ganas nada al agregarconst después de &, no deberías agregarlo: confundirá a la gente: const hará que algunas personas piensen que X es const, como si hubieras dicho “const X& x“.

¿Qué es una “función miembro const”?

Una función miembro que inspecciona (en lugar de mutar) su objeto.

Una función miembro const se indica con un sufijo const justo después de la lista de parámetros de la función miembro. Las funciones de miembro con un sufijo const se denominan “funciones de miembroconst” o ” inspectores. Las “funciones miembro sin sufijoconst se denominan” funciones miembro noconst “o” mutadores.”

class Fred {public: void inspect() const; // This member promises NOT to change *this void mutate(); // This member function might change *this};void userCode(Fred& changeable, const Fred& unchangeable){ changeable.inspect(); // Okay: doesn't change a changeable object changeable.mutate(); // Okay: changes a changeable object unchangeable.inspect(); // Okay: doesn't change an unchangeable object unchangeable.mutate(); // ERROR: attempt to change unchangeable object}

El intento de llamar a unchangeable.mutate() es un error detectado en tiempo de compilación. No hay espacio de tiempo de ejecución ni speedpenalty para const, y no es necesario escribir casos de prueba para comprobarlo en tiempo de ejecución.

La función miembro final const on inspect() debe usarse para significar que el método no cambiará el estado de abstracción (visible para el cliente) del objeto. Eso es ligeramente diferente de decir que el método no cambiará los “bits sin procesar” del objeto struct. A los compiladores de C++ no se les permite tomar la interpretación “bit a bit” a menos que puedan resolver el problema de aliasing, que normalmente no se puede resolver (es decir, podría existir un alias noconst que podría modificar el estado del objeto). Otra información (importante) de este problema de aliasing: apuntar a un objeto con un puntero aconstno garantiza que el objeto no cambie; simplemente promete que el objeto no cambiará a través de ese puntero.

¿Cuál es la relación entre una función de retorno por referencia y una función miembro const?

Si desea devolver un miembro de su objeto this por referencia desde un método inspector, debe devolverlo utilizando reference-to-const (const X& inspect() const) o por valor (X inspect() const).

class Person {public: const std::string& name_good() const; // Right: the caller can't change the Person's name std::string& name_evil() const; // Wrong: the caller can change the Person's name int age() const; // Also right: the caller can't change the Person's age // ...};void myCode(const Person& p) // myCode() promises not to change the Person object...{ p.name_evil() = "Igor"; // But myCode() changed it anyway!!}

La buena noticia es que el compilador a menudo lo atrapará si se equivoca. En particular, si devuelve accidentalmente un miembro de su objeto this por referencia noconst, como en Person::name_evil(), el compilador a menudo lo detectará y le dará un error en tiempo de compilación mientras compila las entrañas de, en este caso,Person::name_evil().

La mala noticia es que el compilador no siempre te atrapará: hay algunos casos en los que el compilador simplemente no te enviará un mensaje de error en tiempo de compilación.

Traducción: necesitas pensar. Si eso te asusta, busca otra línea de trabajo; “pensar” no es una palabra de cuatro letras.

Recuerde la “filosofíaconst” extendida a lo largo de esta sección: una función miembro const no debe cambiar (o permitir que una persona que llama cambie) el estado lógico del objeto this (también conocido como estado abstracto, también conocido como estado de significado). Piense en lo que significa un objeto, no en cómo se implementa internamente. La edad y el nombre de una Persona son parte lógica de la Persona, pero el vecino y el empleador de la Persona no lo son. Un método inspector que devuelve parte del estado lógico / abstracto / significado del objeto thisno debe devolver un puntero (o referencia) que no seaconst a esa parte,independientemente de si esa parte está implementada internamente como un miembro de datos directo incrustado físicamente dentro del objetothis o de alguna otra manera.

¿Cuál es el trato con la “sobrecarga de const”?

const la sobrecarga le ayuda a lograr una corrección const.

const la sobrecarga es cuando tiene un método inspector y un método mutador con el mismo nombre y el mismo número y tipos de parámetros. Los dos métodos distintos difieren solo en que el inspector es const y el mutador no esconst.

El uso más común de const sobrecarga es con el operador de subíndice. Por lo general, debe intentar usar una de las plantillas de contenedores estándar, como std::vector, pero si necesita crear su propia clase que tenga un operador de subíndice, esta es la regla general: los operadores de subíndice a menudo vienen en pares.

class Fred { /*...*/ };class MyFredList {public: const Fred& operator (unsigned index) const; // Subscript operators often come in pairs Fred& operator (unsigned index); // Subscript operators often come in pairs // ...};

El operador de subíndice const devuelve una referencia const, por lo que el compilador evitará que las personas que llaman modifiquen/cambien inadvertidamente el Fred. El operador de subíndice que no esconst devuelve una referencia que no esconst, que es su forma de decirle a sus llamantes (y al compilador) que a sus llamantes se les permite modificar el objeto Fred.

Cuando un usuario de su clase MyFredList llama al operador de subíndice, el compilador selecciona a qué sobrecarga llamar basándose en la constancia de su MyFredList. Si la persona que llama tiene un MyFredList a o MyFredList& a, entonces a llamará al operador de subíndice que no esconst, y la persona que llama terminará con una referencia que no esconst a un Fred:

Por ejemplo, supongamos que class Fred tiene un método inspector inspect() const y un método mutador mutate():

void f(MyFredList& a) // The MyFredList is non-const{ // Okay to call methods that inspect (look but not mutate/change) the Fred at a: Fred x = a; // Doesn't change to the Fred at a: merely makes a copy of that Fred a.inspect(); // Doesn't change to the Fred at a: inspect() const is an inspector-method // Okay to call methods that DO change the Fred at a: Fred y; a = y; // Changes the Fred at a a.mutate(); // Changes the Fred at a: mutate() is a mutator-method}

Sin embargo, si la persona que llama tiene un const MyFredList a o un const MyFredList& a, entonces a llamará al subíndice operador const, y la persona que llama terminará con una referencia const a un Fred. Esto permite que la persona que llama inspeccione Fred en a, pero evita que la persona que llama mute/cambie inadvertidamente Fred en a.

void f(const MyFredList& a) // The MyFredList is const{ // Okay to call methods that DON'T change the Fred at a: Fred x = a; a.inspect(); // Compile-time error (fortunately!) if you try to mutate/change the Fred at a: Fred y; a = y; // Fortunately(!) the compiler catches this error at compile-time a.mutate(); // Fortunately(!) the compiler catches this error at compile-time}

La sobrecarga de Const para operadores de subíndice y funcall se ilustra aquí, aquí, aquí, aquí y aquí.

Por supuesto, también puede usar const – sobrecarga para cosas que no sean el operador de subíndice.

¿Cómo puede ayudarme a diseñar mejores clases si distingo el estado lógico del estado físico?

Porque eso te anima a diseñar tus clases desde afuera hacia adentro en lugar de desde adentro hacia afuera, lo que a su vez hace que tus clases y objetos sean más fáciles de entender y usar, más intuitivos, menos propensos a errores y más rápidos. (Bien, eso es una ligera simplificación excesiva. Para entender todos los si y y pero, usted sólo tiene que leer el resto de thisanswer!)

Entendamos esto desde adentro hacia afuera: (deberías) diseñar tus clases desde afuera hacia adentro, pero si eres nuevo en este concepto, es más fácil de entender desde adentro hacia afuera.

En el interior, los objetos tienen un estado físico (concreto o de bits). Este es el estado que es fácil de ver y entender para los programadores; es el estado que estaría allí si la clase fuera solo un estilo C struct.

En el exterior, sus objetos tienen usuarios de su clase, y estos usuarios están restringidos a usar solo public memberfunctions y friend s. Estos usuarios externos también perciben que el objeto tiene un estado, por ejemplo, si el objeto es de clase Rectangle con métodos width(), height() y area(), sus usuarios dirían que esos tres son parte del estado lógico (o abstracto o significativo) del objeto. Para un usuario externo, el objeto Rectangle tiene un área, incluso si esa área se calcula sobre la marcha (por ejemplo, si el método area() devuelve el producto del ancho y alto del objeto). De hecho, y este es el punto importante, sus usuarios no saben y no les importa cómo implementa cualquiera de estos métodos; los usuarios siguen percibiendo, desde su perspectiva, que el objeto tiene lógicamente un estado de anchura, altura y área.

El ejemplo area() muestra un caso en el que el estado lógico puede contener elementos que no se realizan directamente en el estado físico. Lo contrario también es cierto: las clases a veces ocultan intencionalmente parte del estado físico(concreto, bit a bit) de sus objetos a los usuarios — intencionalmente no proporcionan ninguna función miembro public ofriendque permita a los usuarios leer o escribir o incluso conocer este estado oculto. Eso significa que hay bits en el estado físico del objeto que no tienen elementos correspondientes en el estado lógico del objeto.

Como ejemplo de este último caso, un objeto de colección podría almacenar en caché su última búsqueda con la esperanza de mejorar el rendimiento de su próxima búsqueda. Esta caché es ciertamente parte del estado físico del objeto, pero allí hay un detalle de implementación interna que probablemente no será expuesto a los usuarios, probablemente no será parte del estado lógico del objeto. Decir qué es lo que es fácil si piensas desde afuera hacia adentro: si los usuarios del objeto de colección no pueden comprobar el estado de la caché en sí, la caché es transparente y no forma parte del estado lógico del objeto.

¿La constancia de mis funciones miembro públicas debe basarse en lo que el método hace al estado lógico o físico del objeto?

Lógico.

No hay forma de hacer que la siguiente parte sea fácil. Va a doler. La mejor recomendación es sentarse. Y por favor, por su seguridad, asegúrese de que no haya implementos afilados cerca.

Volvamos al ejemplo de objetos de colección. Recordar: hay un método de búsqueda que almacena la última búsqueda con la esperanza de acelerar futuras búsquedas.

Digamos lo que es probablemente obvio: supongamos que el método de búsqueda no hace cambios en ninguno de los estados lógicos del objeto de recolección.

Así que the ha llegado el momento de hacerte daño. ¿Estás lista?

Aquí viene: si el método de búsqueda no hace ningún cambio en el estado lógico del objeto de la colección, pero cambia el estado físico del objeto de la colección (hace un cambio muy real en la caché muy real), ¿el método de búsqueda debe ser const?

La respuesta es un rotundo Sí. (Hay excepciones a todas las reglas, por lo que ” Sí ” debería tener un asterisco al lado,pero la gran mayoría de las veces, la respuesta es Sí.)

Todo esto se trata de” lógico const “sobre” físico const.”Significa que la decisión sobre si decorar amétodo con const debe depender principalmente de si ese método deja el estado lógico sin cambios, independientemente (¿está sentado?) (es posible que desee sentarse) independientemente de si el método produce cambios muy reales en el estado físico muy real del objeto.

En caso de que no se haya hundido, o en caso de que aún no sienta dolor, separémoslo en dos casos:

  • Si un método cambia cualquier parte del estado lógico del objeto, lógicamente es un mutador; no debe ser const evenif (¡como sucede en realidad!) el método no cambia ningún fragmento físico del estado concreto del objeto.
  • Por el contrario, un método es lógicamente un inspector y debe ser const si nunca cambia ninguna parte del estado lógico del objeto, incluso si (¡como sucede realmente!) el método cambia los bits físicos del estado concreto del objeto.

Si está confundido, léalo de nuevo.

Si no estás confundido pero estás enojado, bien: puede que aún no te guste, pero al menos lo entiendes. Respire hondo y repita después de mí: “La constness de un método debe tener sentido desde fuera del objeto.”

Si aún está enojado, repita esto tres veces: “La constancia de un método debe tener sentido para los usuarios del objeto, y esos usuarios solo pueden ver el estado lógico del objeto.”

Si todavía estás enojado, lo siento, es lo que es. Aguántate y vive con ello. Sí, habrá excepciones; todas las reglas las tienen. Pero, por regla general, esta noción lógica const es buena para usted y para su software.

Una cosa más. Esto se va a poner absurdo, pero seamos precisos sobre si un método cambia el estado lógico del objeto. Si está fuera de la clase, es un usuario normal, cada experimento que pueda realizar (cada método o secuencia de métodos que llame) tendría los mismos resultados (mismos valores de retorno, mismas excepciones o falta de excepciones)independientemente de si llamó primero a ese método de búsqueda. Si la función de búsqueda cambió cualquier comportamiento futuro de cualquier método futuro (no solo lo hizo más rápido, sino que cambió el resultado, cambió el valor devuelto, cambió la excepción), entonces el método de búsqueda cambió el estado lógico del objeto, es un mutuador. Pero si el método de búsqueda no cambió nada más que quizás hacer algunas cosas más rápidas, entonces es un inspector.

¿Qué hago si quiero que una función de miembro const realice un cambio “invisible” en un miembro de datos?

Use mutable (o, como último recurso, use const_cast).

Un pequeño porcentaje de inspectores necesita realizar cambios en el estado físico de un objeto que no pueden ser observados por usuarios externos, cambios en el estado físico pero no lógico.

Por ejemplo, el objeto de colección discutido anteriormente almacenó en caché su última búsqueda con la esperanza de mejorar el rendimiento de su próxima búsqueda. Dado que la caché, en este ejemplo, no puede ser observada directamente por ninguna parte de la interfaz pública del objeto de colección (que no sea el tiempo), su existencia y estado no forma parte del estado lógico del objeto, por lo que los cambios en él son invisibles para los usuarios externos. El método lookup es un inspector, ya que nunca cambia el estado lógico del objeto, independientemente del hecho de que, al menos para la implementación actual, cambie el estado físico del objeto.

Cuando los métodos cambian el estado físico pero no lógico, el método generalmente debe marcarse como const, ya que realmente es un método inspector. Esto crea un problema: cuando el compilador vea que su método const cambia el estado físico del objeto this, se quejará-le dará a su código un mensaje de error.

El lenguaje del compilador C++ utiliza la palabra clave mutable para ayudarle a adoptar esta noción lógica const. En este caso, marcaría la caché con la palabra clave mutable, de esa manera el compilador sabe que está permitido cambiar dentro de un métodoconst o a través de cualquier otro puntero o referencia const. En nuestra jerga, la palabra clave mutable marca las porciones del estado físico del objeto que no forman parte del estado lógico.

La palabra clave mutable va justo antes de la declaración del miembro de datos, es decir, el mismo lugar donde podría colocarconst. El otro enfoque, no preferido, es desechar la const‘ness del puntero this, probablemente a través de la palabra claveconst_cast :

Set* self = const_cast<Set*>(this); // See the NOTE below before doing this!

Después de esta línea, self tendrá los mismos bits que this, es decir, self == this, pero self es un Set* en lugar de unconst Set* (técnicamente this es un const Set* const, pero el const más correcto es irrelevante para esta discusión).Esto significa que puede usar self para modificar el objeto al que apunta this.

NOTA: hay un error extremadamente improbable que puede ocurrir con const_cast. Solo sucede cuando se combinan tres rarezas al mismo tiempo: un miembro de datos que debería ser mutable (como se discutió anteriormente), un compilador que no admite la palabra clave mutable y/o un programador que no la usa, y un objeto que originalmente se definió como const (en oposición a un objeto normal, noconst al que apunta un puntero aconst).Aunque esta combinación es tan rara que puede que nunca le suceda, si alguna vez sucedió, el código puede no funcionar (el estándar dice que el comportamiento es indefinido).

Si alguna vez desea usar const_cast, use mutable en su lugar. En otras palabras, si alguna vez necesita cambiar un miembro de un objeto, y ese objeto es apuntado por un puntero aconst, lo más seguro y sencillo es agregar mutable a la declaración del miembro. Puede usar const_cast si está seguro de que el objeto real no es const (por ejemplo, si está seguro de que el objeto está declarado algo como esto: Set s;), pero si el objeto en sí puede ser const (por ejemplo, si puede ser declarado como: const Set s;), use mutable en lugar de const_cast.

Por favor, no escriba diciendo que la versión X del compilador Y en la máquina Z le permite cambiar un miembro que no seamutable de un objetoconst. No me importa, es ilegal de acuerdo con el lenguaje y su código probablemente fallará en un compilador diferente o incluso en una versión diferente (una actualización) del mismo compilador. Solo di que no. Utilice mutable en su lugar. Escribir código que está garantizado que funciona, no código que no parece romperse.

¿const_cast significa oportunidades de optimización perdidas?

En teoría, sí; en la práctica, no.

Incluso si el lenguaje fuera ilegal const_cast , la única manera de evitar vaciar la caché de registro a través de una llamada a const memberfunction sería resolver el problema de aliasing (p. ej., para demostrar que no hay punteros que no seanconst que apunten al objeto). Esto puede suceder solo en casos raros (cuando el objeto se construye en el ámbito de la invocación a la función miembro const, y cuando todas las invocaciones a funciones miembro noconst entre la construcción del objeto y la invocación a funciones miembroconst están enlazadas estáticamente, y cuando cada una de estas invocaciones es también inlined, y cuando el constructor en sí es inlined, y cuando cualquier función miembro las llamadas al constructor son inline).

¿Por qué el compilador me permite cambiar un int después de que lo he apuntado con un const int*?

Porque “const int* p “significa” p promete no cambiar el *p, “not” *p promete no cambiar.”

Hacer que un const int* apunte a un int no hace const-define el int. El int no se puede cambiar a través delconst int*, pero si alguien tiene un int* (nota: no const) que apunta a (“alias”) el mismo int, entonces eseint* se puede usar para cambiar el int. Por ejemplo:

void f(const int* p1, int* p2){ int i = *p1; // Get the (original) value of *p1 *p2 = 7; // If p1 == p2, this will also change *p1 int j = *p1; // Get the (possibly new) value of *p1 if (i != j) { std::cout << "*p1 changed, but it didn't change via pointer p1!\n"; assert(p1 == p2); // This is the only way *p1 could be different }}int main(){ int x = 5; f(&x, &x); // This is perfectly legal (and even moral!) // ...}

Tenga en cuenta que main() y f(const int*,int*) pueden estar en diferentes unidades de compilación que se compilan en diferentes días de la semana. En ese caso, no hay forma de que el compilador pueda detectar el alias en tiempo de compilación. Por lo tanto, no hay manera de que podamos hacer una regla de lenguaje que prohíba este tipo de cosas. De hecho, ni siquiera querríamos hacer tal arule, ya que en general se considera una característica que puede tener muchos punteros que apuntan a la misma cosa. El hecho de que uno de esos indicadores promete no cambiar la “cosa” subyacente es solo una promesa hecha por el puntero; no es una promesa hecha por la “cosa”.

¿”const Fred* p” significa que *p no puede cambiar?

¡No! (Esto está relacionado con las preguntas frecuentes sobre aliasing de punteros int.)

const Fred* p” significa que Fred no se puede cambiar a través del puntero p, pero puede haber otras formas de llegar al objeto sin pasar por un const (como un puntero noconst con alias, como un Fred*). Por ejemplo, si tiene dos punteros ” const Fred* p” y “Fred* q ” que apuntan al mismo objeto Fred (alias), el puntero q se puede usar para cambiar el objeto Fred, pero el puntero p no puede.

class Fred {public: void inspect() const; // A const member function void mutate(); // A non-const member function};int main(){ Fred f; const Fred* p = &f; Fred* q = &f; p->inspect(); // Okay: No change to *p p->mutate(); // Error: Can't change *p via p q->inspect(); // Okay: q is allowed to inspect the object q->mutate(); // Okay: q is allowed to mutate the object f.inspect(); // Okay: f is allowed to inspect the object f.mutate(); // Okay: f is allowed to mutate the object // ...}

¿Por qué recibo un error al convertir un Foo** → const Foo**?

Porque convertir Foo**const Foo** sería inválido y peligroso.

C++ permite la conversión (segura) Foo*Foo const*, pero da un error si intenta convertir implícitamente Foo**const Foo**.

La justificación de por qué ese error es algo bueno se da a continuación. Pero primero, aquí está la solución más común: cambio simple const Foo** a const Foo* const*:

class Foo { /* ... */ };void f(const Foo** p);void g(const Foo* const* p);int main(){ Foo** p = /*...*/; // ... f(p); // ERROR: it's illegal and immoral to convert Foo** to const Foo** g(p); // Okay: it's legal and moral to convert Foo** to const Foo* const* // ...}

La razón por la que la conversión de Foo**const Foo** es peligrosa es que le permitiría modificar de forma silenciosa y accidental un objeto const Foo sin un molde:

class Foo {public: void modify(); // make some modification to the this object};int main(){ const Foo x; Foo* p; const Foo** q = &p; // q now points to p; this is (fortunately!) an error *q = &x; // p now points to x p->modify(); // Ouch: modifies a const Foo!! // ...}

Si la línea q = &p fuera legal, q estaría apuntando a p. La siguiente línea, *q = &x, cambia p en sí misma (ya que *q es p) para apuntar a x. Eso sería algo malo, ya que habríamos perdido el calificador const: p es un Foo* perox es un const Foo. La línea p->modify() explota la capacidad de pde modificar su referente, que es el verdadero problema, ya que terminamos modificando un const Foo.

Por analogía, si escondes a un criminal bajo un disfraz legal, puede explotar la confianza que se le da a ese disfraz.Eso es malo.

Afortunadamente C++ le impide hacer esto: la línea q = &p está marcada por el compilador de C++ como un error de tiempo de compilación. Recordatorio: por favor, no utilice el puntero para evitar ese mensaje de error en tiempo de compilación. ¡Di Que No!

(Nota: existe una similitud conceptual entre esto y la prohibición de convertir Derived** aBase**.)

Deja una respuesta

Tu dirección de correo electrónico no será publicada.