En el Effective C++
elemento 03, utilice constante siempre que sea posible.
class Bigint
{
int _data[MAXLEN];
//...
public:
int& operator[](const int index) { return _data[index]; }
const int operator[](const int index) const { return _data[index]; }
//...
};
const int operator[]
hace la diferencia de int& operator[]
.
Pero que pasa:
int foo() { }
y
const int foo() { }
Parece que son iguales.
Mi pregunta es, ¿por qué usamos en const int operator[](const int index) const
lugar de int operator[](const int index) const
?
int operator[](int i)
versusconst int operator[](const int i)
.Bigint&
oa const Bigint&
.const int i
discusión.Respuestas:
Se ignoran los calificadores de cv de nivel superior en tipos de devolución de tipos que no son de clase. Lo que significa que incluso si escribe:
int const foo();
El tipo de retorno es
int
. Si el tipo de retorno es una referencia, por supuesto,const
ya no es el nivel superior y la distinción entre:int& operator[]( int index );
y
int const& operator[]( int index ) const;
es significante. (Tenga en cuenta también que en las declaraciones de funciones, como las anteriores, también se ignoran los calificadores cv de nivel superior).
La distinción también es relevante para los valores de retorno de tipo de clase: si regresa
T const
, entonces la persona que llama no puede llamar a funciones no constantes en el valor devuelto, por ejemplo:class Test { public: void f(); void g() const; }; Test ff(); Test const gg(); ff().f(); // legal ff().g(); // legal gg().f(); // **illegal** gg().g(); // legal
fuente
()
despuésff
ygg
. Los agregaré. Gracias por la corrección.const
es útil, sin embargo, muchas personas parecen pasarla por alto o insistir en que devolver unconst
valor no tiene ninguna utilidad.int const
, por ejemplo, es equivalente a una llamada de esa función modificada para regresarint
. Standardese en C ++ 11 §3.10 / 4, “Los valores de clase pueden tener tipos calificados por cv; los prvalues que no son de clase siempre tienen tipos cv-no calificados ”.Debe distinguir claramente entre el uso constante que se aplica a los valores de retorno, los parámetros y la función en sí.
Valores devueltos
Considere
const std::string SomeMethod() const
. No permitirá(std::string&&)
que se use la función, ya que espera rvalue no constante. En otras palabras, la cadena devuelta siempre se copiará.const
protege el objeto devuelto de ser modificado.Parámetros
Función en sí
const
al final, solo puede ejecutar otrasconst
funciones y no puede modificar o permitir la modificación de los datos de la clase. Por lo tanto, si regresa por referencia, la referencia devuelta debe ser constante. Solo las funciones const se pueden llamar en un objeto o una referencia a un objeto que es const en sí mismo. También se pueden cambiar los campos mutables.this
referenciaT const*
. La función siempre puedeconst_cast
this
, pero, por supuesto, esto no se debe hacer y se considera inseguro.Conclusión
Si su método no modifica y nunca modificará las variables de clase, márquelo como constante y asegúrese de cumplir con todos los criterios necesarios. Permitirá que se escriba un código más limpio, manteniéndolo const-correcto . Sin embargo, poner en
const
todas partes sin pensar en ello ciertamente no es el camino a seguir.fuente
const
se ignora para los tipos que no son de clase . Importa para los tipos de clases. Y para los parámetros pasados por valor, el nivel superiorconst
se ignora en las declaraciones de funciones. Solo tiene significado en la definición.const
a una función: modifica el tipo dethis
, que esT const*
, en lugar deT*
. Todos los demás efectos se derivan de esto. (Y no evita todas las modificaciones del objeto: losmutable
datos aún se pueden cambiar, y la función puede desechar legalmente const y cambiar lo que desee).Hay poco valor en agregar
const
calificaciones a valores r que no son de referencia / no punteros, y no tiene sentido agregarlos a los valores integrados.En el caso de los tipos definidos por el usuario , una
const
calificación evitará que las personas que llaman invoquen unaconst
función no miembro en el objeto devuelto. Por ejemplo, dadoconst std::string foo(); std::string bar();
luego
foo().resize(42);
estaría prohibido, mientras
bar().resize(4711);
estaría permitido.
Para integradas como
int
, esto no tiene ningún sentido, porque tales valores r no se pueden modificar de todos modos.(Sin embargo, recuerdo que Effective C ++ discutió cómo hacer el tipo de retorno de
operator=()
unaconst
referencia, y esto es algo a considerar).Editar:
Parece que Scott dio ese consejo . Si es así, debido a las razones dadas anteriormente, lo encuentro cuestionable incluso para C ++ 98 y C ++ 03 . Para C ++ 11, lo considero claramente incorrecto , como parece haber descubierto el propio Scott. En las erratas para C ++ efectivo, 3ª ed. , escribe (o cita a otros que se quejaron):
Y después:
fuente
Es posible que pierda el sentido del consejo de Meyers. La diferencia esencial está en el
const
modificador del método.Este es un método no constante (observe no
const
al final) lo que significa que puede modificar el estado de la clase.int& operator[](const int index)
Este es un método constante (aviso
const
al final)const int operator[](const int index) const
¿Qué pasa con los tipos de parámetro y el valor de retorno? Hay una pequeña diferencia entre
int
yconst int
, pero no es relevante para el punto del consejo. A lo que debe prestar atención es que la sobrecarga no constante devuelve, loint&
que significa que puede asignarle, por ejemplonum[i]=0
, y la sobrecarga constante devuelve un valor no modificable (sin importar si el tipo de retorno esint
oconst int
).En mi opinión personal, si un objeto se pasa por valor , el
const
modificador es superfluo. Esta sintaxis es más corta y logra el mismoint& operator[](int index); int operator[](int index) const;
fuente
const
valor.foo
ejemplo con eloperator[]
ejemplo. Podría perder el punto de que elconst
modificador para el método marca la diferencia, no el tipo de retorno.La razón principal para devolver valores como const es que no puede decir algo como
foo() = 5;
. En realidad, esto no es un problema con los tipos primitivos, ya que no se pueden asignar valores r de tipos primitivos, pero es un problema con los tipos definidos por el usuario (como(a + b) = c;
, con una sobrecargaoperator+
).Siempre he encontrado la justificación para eso bastante endeble. No se puede detener a alguien que tiene la intención de escribir un código incómodo, y este tipo particular de coerción no tiene ningún beneficio real en mi opinión.
Con C ++ 11, en realidad hay mucho daño que este idioma está haciendo: devolver valores como const evita las optimizaciones de movimiento y, por lo tanto, debe evitarse siempre que sea posible. Básicamente, ahora consideraría esto como un anti-patrón.
Aquí hay un artículo relacionado tangencialmente sobre C ++ 11.
fuente
Cuando se escribió ese libro, el consejo fue de poca utilidad, pero sirvió para evitar que el usuario escribiera, por ejemplo,
foo() = 42;
y esperara que cambiara algo persistente.En el caso de
operator[]
, esto puede ser un poco confuso si no proporciona también una noconst
sobrecarga que devuelva una noconst
referencia, aunque quizás pueda evitar esa confusión devolviendo unaconst
referencia o un objeto proxy en lugar de un valor.En estos días, es un mal consejo, ya que le impide vincular el resultado a una
const
referencia (no ) de valor.(Como se señaló en los comentarios, la pregunta es discutible cuando se devuelve un tipo primitivo como
int
, ya que el lenguaje le impide asignar a unorvalue
de ese tipo; estoy hablando del caso más general que incluye la devolución de tipos definidos por el usuario. )fuente
int
.foo() = 42;
es ilegal, independientemente de si el tipo de retorno se declaraint
oint const
. No creo que Scott defendiera el uso deconst
en lugares como tipos de retorno y parámetros en declaraciones de funciones, donde el compilador lo ignora.Para tipos primitivos (como
int
), la consistencia del resultado no importa. Para las clases, podría cambiar el comportamiento. Por ejemplo, es posible que no pueda llamar a un método que no sea constante en el resultado de su función:class Bigint { const C foo() const { ... } ... } Bigint b; b.foo().bar();
Lo anterior está prohibido si
bar()
es una función de miembro constante deC
. En general, elija lo que tenga sentido.fuente
Mira eso:
const int operator[](const int index) const
el
const
final de la declaración. Describe que este método se puede llamar en constantes.En la otra mano cuando escribes solo
int& operator[](const int index)
solo se puede llamar en instancias no constantes y también proporciona:
big_int[0] = 10;
sintaxis.
fuente
Una de sus sobrecargas es devolver una referencia a un elemento en la matriz, que luego puede cambiar.
int& operator[](const int index) { return _data[index]; }
La otra sobrecarga devuelve un valor para que lo use.
const int operator[](const int index) const { return _data[index]; }
Dado que llama a cada una de estas sobrecargas de la misma manera, una nunca cambiará el valor cuando se use.
int foo = myBigInt[1]; // Doesn't change values inside the object. myBigInt[1] = 2; // Assigns a new value at index `
fuente
En el ejemplo del operador de indexación de matrices (
operator[]
), hace una diferencia.Con
int& operator[](const int index) { /* ... */ }
puede usar la indexación para cambiar directamente la entrada en la matriz, por ejemplo, usándola así:
mybigint[3] = 5;
El segundo, el
const int operator[](const int)
operador se utiliza para obtener el valor únicamente.Sin embargo, como valor de retorno de funciones, para tipos simples como, por ejemplo
int
, no importa. Lo que importa es si está devolviendo tipos más complejos, digamos astd::vector
, y no desea que la persona que llama a la función modifique el vector.fuente
El
const
tipo de devolución no es tan importante aquí. Desde temporales int no son modificables, no hay ninguna diferencia observable en el usoint
vsconst int
. Vería una diferencia si utilizara un objeto más complejo que pudiera modificarse.fuente
Hay algunas buenas respuestas que giran en torno al tecnicismo de las dos versiones. Para un valor primitivo, no hace ninguna diferencia.
Sin embargo , siempre he considerado
const
estar ahí para el programador más que para el compilador. Cuando escribeconst
, está diciendo explícitamente "esto no debería cambiar". Seamos sinceros,const
generalmente se puede eludir de todos modos, ¿verdad?Cuando devuelve un
const
, le está diciendo al programador que usa esa función que el valor no debería cambiar. Y si lo está cambiando, probablemente esté haciendo algo mal, porque no debería tener que hacerlo.EDITAR: También creo que "usar
const
siempre que sea posible" es un mal consejo. Debería usarlo donde tenga sentido.fuente