C++
Const Correccion
Buscar..
Sintaxis
- class ClassOne {public: bool non_modifying_member_function () const {/ * ... * /}};
- int ClassTwo :: non_modifying_member_function () const {/ * ... * /}
- void ClassTwo :: modifying_member_function () {/ * ... * /}
- char non_param_modding_func (const ClassOne & one, const ClassTwo * two) {/ * ... * /}
- float parameters_modifying_function (ClassTwo & one, ClassOne * two) {/ * ... * /}
- short ClassThree :: non_modding_non_param_modding_f (const ClassOne &) const {/ * ... * /}
Observaciones
const
corrección de const
es una herramienta de solución de problemas muy útil, ya que le permite al programador determinar rápidamente qué funciones podrían estar modificando inadvertidamente el código. También evita que los errores involuntarios, como el que se muestra en Const Correct Function Parameters
, se compilen correctamente y pasen desapercibidos.
Es mucho más fácil diseñar una clase para la corrección const
, que luego agregar la corrección const
a una clase preexistente. Si es posible, diseñar cualquier clase que puede ser const
correcta para que sea const
correcta, para salvar a sí mismo ya otros la molestia de tarde modificándolo.
Tenga en cuenta que esto también puede aplicarse a la corrección volatile
si es necesario, con las mismas reglas que para la corrección const
, pero esto se usa con mucha menos frecuencia.
Refrences:
Los basicos
const
corrección const
es la práctica de diseñar código de modo que solo el código que necesita modificar una instancia pueda modificar una instancia (es decir, tenga acceso de escritura) y, a la inversa, que cualquier código que no necesite modificar una instancia no pueda hacerlo entonces (es decir, solo tiene acceso de lectura). Esto evita que la instancia se modifique involuntariamente, lo que hace que el código sea menos propenso a errores, y documenta si el código está destinado a cambiar el estado de la instancia o no. También permite que las instancias se traten como const
siempre que no necesiten ser modificadas, o definidas como const
si no es necesario cambiarlas después de la inicialización, sin perder ninguna funcionalidad.
Esto se hace dando a los miembros las funciones const
CV-qualifiers , y haciendo const
parámetros de puntero / referencia, excepto en el caso de que necesiten acceso de escritura.
class ConstCorrectClass {
int x;
public:
int getX() const { return x; } // Function is const: Doesn't modify instance.
void setX(int i) { x = i; } // Not const: Modifies instance.
};
// Parameter is const: Doesn't modify parameter.
int const_correct_reader(const ConstCorrectClass& c) {
return c.getX();
}
// Parameter isn't const: Modifies parameter.
void const_correct_writer(ConstCorrectClass& c) {
c.setX(42);
}
const ConstCorrectClass invariant; // Instance is const: Can't be modified.
ConstCorrectClass variant; // Instance isn't const: Can be modified.
// ...
const_correct_reader(invariant); // Good. Calling non-modifying function on const instance.
const_correct_reader(variant); // Good. Calling non-modifying function on modifiable instance.
const_correct_writer(variant); // Good. Calling modifying function on modifiable instance.
const_correct_writer(invariant); // Error. Calling modifying function on const instance.
Debido a la naturaleza de la corrección de const, esto comienza con las funciones de los miembros de la clase y se abre camino hacia afuera; Si intenta llamar a una función miembro no const
desde una instancia const
o desde una instancia no const
tratada como const
, el compilador le dará un error sobre la pérdida de calificadores cv.
Diseño correcto de la clase de Const
En una clase const
, todas las funciones miembro que no cambian el estado lógico tienen this
cv calificado como const
, lo que indica que no modifican el objeto (aparte de mutable
campos mutable
, que se pueden modificar libremente incluso en casos const
); si una función calificada para cv const
devuelve una referencia, esa referencia también debe ser una const
. Esto permite que se los llame en instancias constantes y no calificadas para CV, ya que una const T*
es capaz de vincularse a una T*
o una const T*
. Esto, a su vez, permite que las funciones declaren sus parámetros pasados por referencia como const
cuando no necesitan ser modificados, sin perder ninguna funcionalidad.
Además, en una clase correcta const
, todos los parámetros de función pasados por referencia serán const
correctos, como se describe en Const Correct Function Parameters
, de modo que solo pueden modificarse cuando la función necesita modificarlos explícitamente.
Primero, echemos un vistazo a this
calificadores cv:
// Assume class Field, with member function "void insert_value(int);".
class ConstIncorrect {
Field fld;
public:
ConstIncorrect(Field& f); // Modifies.
Field& getField(); // Might modify. Also exposes member as non-const reference,
// allowing indirect modification.
void setField(Field& f); // Modifies.
void doSomething(int i); // Might modify.
void doNothing(); // Might modify.
};
ConstIncorrect::ConstIncorrect(Field& f) : fld(f) {} // Modifies.
Field& ConstIncorrect::getField() { return fld; } // Doesn't modify.
void ConstIncorrect::setField(Field& f) { fld = f; } // Modifies.
void ConstIncorrect::doSomething(int i) { // Modifies.
fld.insert_value(i);
}
void ConstIncorrect::doNothing() {} // Doesn't modify.
class ConstCorrectCVQ {
Field fld;
public:
ConstCorrectCVQ(Field& f); // Modifies.
const Field& getField() const; // Doesn't modify. Exposes member as const reference,
// preventing indirect modification.
void setField(Field& f); // Modifies.
void doSomething(int i); // Modifies.
void doNothing() const; // Doesn't modify.
};
ConstCorrectCVQ::ConstCorrectCVQ(Field& f) : fld(f) {}
Field& ConstCorrectCVQ::getField() const { return fld; }
void ConstCorrectCVQ::setField(Field& f) { fld = f; }
void ConstCorrectCVQ::doSomething(int i) {
fld.insert_value(i);
}
void ConstCorrectCVQ::doNothing() const {}
// This won't work.
// No member functions can be called on const ConstIncorrect instances.
void const_correct_func(const ConstIncorrect& c) {
Field f = c.getField();
c.do_nothing();
}
// But this will.
// getField() and doNothing() can be called on const ConstCorrectCVQ instances.
void const_correct_func(const ConstCorrectCVQ& c) {
Field f = c.getField();
c.do_nothing();
}
Entonces podemos combinar esto con Const Correct Function Parameters
, causando la clase sea plenamente const
-correct.
class ConstCorrect {
Field fld;
public:
ConstCorrect(const Field& f); // Modifies instance. Doesn't modify parameter.
const Field& getField() const; // Doesn't modify. Exposes member as const reference,
// preventing indirect modification.
void setField(const Field& f); // Modifies instance. Doesn't modify parameter.
void doSomething(int i); // Modifies. Doesn't modify parameter (passed by value).
void doNothing() const; // Doesn't modify.
};
ConstCorrect::ConstCorrect(const Field& f) : fld(f) {}
Field& ConstCorrect::getField() const { return fld; }
void ConstCorrect::setField(const Field& f) { fld = f; }
void ConstCorrect::doSomething(int i) {
fld.insert_value(i);
}
void ConstCorrect::doNothing() const {}
Esto también se puede combinar con la sobrecarga basada en const
, en el caso de que queramos un comportamiento si la instancia es const
, y un comportamiento diferente si no lo es; un uso común para esto es constainers que proporcionan accesores que solo permiten modificaciones si el contenedor en sí no es const
.
class ConstCorrectContainer {
int arr[5];
public:
// Subscript operator provides read access if instance is const, or read/write access
// otherwise.
int& operator[](size_t index) { return arr[index]; }
const int& operator[](size_t index) const { return arr[index]; }
// ...
};
Esto es comúnmente utilizado en la biblioteca estándar, con la mayoría de los recipientes proporcionando sobrecargas para tomar const
Ness en cuenta.
Constar los parámetros de función correcta
En una función de corrección const
, todos los parámetros pasados por referencia se marcan como const
menos que la función los modifique directa o indirectamente, lo que evita que el programador cambie inadvertidamente algo que no pretendían cambiar. Esto permite que la función de tomar tanto const
y los casos no-CV-cualificado, ya su vez, hace que la instancia está this
a ser de tipo const T*
cuando un miembro de la función se llama, donde T
es el tipo de clase.
struct Example {
void func() { std::cout << 3 << std::endl; }
void func() const { std::cout << 5 << std::endl; }
};
void const_incorrect_function(Example& one, Example* two) {
one.func();
two->func();
}
void const_correct_function(const Example& one, const Example* two) {
one.func();
two->func();
}
int main() {
Example a, b;
const_incorrect_function(a, &b);
const_correct_function(a, &b);
}
// Output:
3
3
5
5
Si bien los efectos de esto son menos evidentes que los de const
diseño de la clase correcta (en ese const
funciones -correct y const
clases -incorrect causará errores de compilación, mientras que const
clases -correct y const
funciones -incorrect compilará correctamente), const
correcta las funciones detectarán una gran cantidad de errores que las funciones const
incorrectas dejarán pasar, como la que se muestra a continuación. [Nota, sin embargo, que un const
función -incorrect causará errores de compilación si se aprueba una const
ejemplo, cuando se espera que un no const
uno.]
// Read value from vector, then compute & return a value.
// Caches return values for speed.
template<typename T>
const T& bad_func(std::vector<T>& v, Helper<T>& h) {
// Cache values, for future use.
// Once a return value has been calculated, it's cached & its index is registered.
static std::vector<T> vals = {};
int v_ind = h.get_index(); // Current working index for v.
int vals_ind = h.get_cache_index(v_ind); // Will be -1 if cache index isn't registered.
if (vals.size() && (vals_ind != -1) && (vals_ind < vals.size()) && !(h.needs_recalc())) {
return vals[h.get_cache_index(v_ind)];
}
T temp = v[v_ind];
temp -= h.poll_device();
temp *= h.obtain_random();
temp += h.do_tedious_calculation(temp, v[h.get_last_handled_index()]);
// We're feeling tired all of a sudden, and this happens.
if (vals_ind != -1) {
vals[vals_ind] = temp;
} else {
v.push_back(temp); // Oops. Should've been accessing vals.
vals_ind = vals.size() - 1;
h.register_index(v_ind, vals_ind);
}
return vals[vals_ind];
}
// Const correct version. Is identical to above version, so most of it shall be skipped.
template<typename T>
const T& good_func(const std::vector<T>& v, Helper<T>& h) {
// ...
// We're feeling tired all of a sudden, and this happens.
if (vals_ind != -1) {
vals[vals_ind] = temp;
} else {
v.push_back(temp); // Error: discards qualifiers.
vals_ind = vals.size() - 1;
h.register_index(v_ind, vals_ind);
}
return vals[vals_ind];
}
Constancia de la corrección como documentación
Una de las cosas más útiles acerca de la corrección const
es que sirve como una forma de documentar el código, proporcionando ciertas garantías al programador y otros usuarios. Estas garantías son impuestas por el compilador debido a la const
, con una falta de const
a su vez indica que el código no las proporciona.
Funciones de miembros calificados para CV const
:
- Se puede asumir que cualquier función miembro que sea
const
tiene intención de leer la instancia, y:- No modificará el estado lógico de la instancia a la que se llama. Por lo tanto, no deben modificar ninguna variable miembro de la instancia a la que se llama, excepto las variables
mutable
. - No debe llamar a ninguna otra función que pueda modificar ninguna variable miembro de la instancia, excepto las variables
mutable
.
- No modificará el estado lógico de la instancia a la que se llama. Por lo tanto, no deben modificar ninguna variable miembro de la instancia a la que se llama, excepto las variables
- A la inversa, se puede asumir que cualquier función miembro que no sea
const
tiene la intención de modificar la instancia, y:- Puede o no puede modificar el estado lógico.
- Puede o no llamar a otras funciones que modifican el estado lógico.
Esto se puede usar para hacer suposiciones sobre el estado del objeto después de que se llame a cualquier función miembro dada, incluso sin ver la definición de esa función:
// ConstMemberFunctions.h
class ConstMemberFunctions {
int val;
mutable int cache;
mutable bool state_changed;
public:
// Constructor clearly changes logical state. No assumptions necessary.
ConstMemberFunctions(int v = 0);
// We can assume this function doesn't change logical state, and doesn't call
// set_val(). It may or may not call squared_calc() or bad_func().
int calc() const;
// We can assume this function doesn't change logical state, and doesn't call
// set_val(). It may or may not call calc() or bad_func().
int squared_calc() const;
// We can assume this function doesn't change logical state, and doesn't call
// set_val(). It may or may not call calc() or squared_calc().
void bad_func() const;
// We can assume this function changes logical state, and may or may not call
// calc(), squared_calc(), or bad_func().
void set_val(int v);
};
Debido a las reglas const
, estos supuestos serán de hecho aplicados por el compilador.
// ConstMemberFunctions.cpp
ConstMemberFunctions::ConstMemberFunctions(int v /* = 0*/)
: cache(0), val(v), state_changed(true) {}
// Our assumption was correct.
int ConstMemberFunctions::calc() const {
if (state_changed) {
cache = 3 * val;
state_changed = false;
}
return cache;
}
// Our assumption was correct.
int ConstMemberFunctions::squared_calc() const {
return calc() * calc();
}
// Our assumption was incorrect.
// Function fails to compile, due to `this` losing qualifiers.
void ConstMemberFunctions::bad_func() const {
set_val(863);
}
// Our assumption was correct.
void ConstMemberFunctions::set_val(int v) {
if (v != val) {
val = v;
state_changed = true;
}
}
Parámetros de la función const
:
- Se puede suponer que cualquier función con uno o más parámetros que sean
const
tiene intención de leer esos parámetros, y:- No modificará esos parámetros ni llamará a ninguna función miembro que los modifique.
- No debe pasar esos parámetros a ninguna otra función que los modifique y / o llame a ninguna función miembro que los modifique.
- A la inversa, se puede suponer que cualquier función con uno o más parámetros que no son
const
tiene la intención de modificar esos parámetros, y:- Puede o no modificar esos parámetros, o llamar a cualquier función miembro que los modifique.
- Pueden o no pasar esos parámetros a otras funciones que los modificarían y / o llamarían a cualquier función miembro que los modificaría.
Esto se puede usar para hacer suposiciones sobre el estado de los parámetros después de pasar a cualquier función dada, incluso sin ver la definición de esa función.
// function_parameter.h
// We can assume that c isn't modified (and c.set_val() isn't called), and isn't passed
// to non_qualified_function_parameter(). If passed to one_const_one_not(), it is the first
// parameter.
void const_function_parameter(const ConstMemberFunctions& c);
// We can assume that c is modified and/or c.set_val() is called, and may or may not be passed
// to any of these functions. If passed to one_const_one_not, it may be either parameter.
void non_qualified_function_parameter(ConstMemberFunctions& c);
// We can assume that:
// l is not modified, and l.set_val() won't be called.
// l may or may not be passed to const_function_parameter().
// r is modified, and/or r.set_val() may be called.
// r may or may not be passed to either of the preceding functions.
void one_const_one_not(const ConstMemberFunctions& l, ConstMemberFunctions& r);
// We can assume that c isn't modified (and c.set_val() isn't called), and isn't passed
// to non_qualified_function_parameter(). If passed to one_const_one_not(), it is the first
// parameter.
void bad_parameter(const ConstMemberFunctions& c);
Debido a las reglas const
, estos supuestos serán de hecho aplicados por el compilador.
// function_parameter.cpp
// Our assumption was correct.
void const_function_parameter(const ConstMemberFunctions& c) {
std::cout << "With the current value, the output is: " << c.calc() << '\n'
<< "If squared, it's: " << c.squared_calc()
<< std::endl;
}
// Our assumption was correct.
void non_qualified_function_parameter(ConstMemberFunctions& c) {
c.set_val(42);
std::cout << "For the value 42, the output is: " << c.calc() << '\n'
<< "If squared, it's: " << c.squared_calc()
<< std::endl;
}
// Our assumption was correct, in the ugliest possible way.
// Note that const correctness doesn't prevent encapsulation from intentionally being broken,
// it merely prevents code from having write access when it doesn't need it.
void one_const_one_not(const ConstMemberFunctions& l, ConstMemberFunctions& r) {
// Let's just punch access modifiers and common sense in the face here.
struct Machiavelli {
int val;
int unimportant;
bool state_changed;
};
reinterpret_cast<Machiavelli&>(r).val = l.calc();
reinterpret_cast<Machiavelli&>(r).state_changed = true;
const_function_parameter(l);
const_function_parameter(r);
}
// Our assumption was incorrect.
// Function fails to compile, due to `this` losing qualifiers in c.set_val().
void bad_parameter(const ConstMemberFunctions& c) {
c.set_val(18);
}
Si bien es posible eludir la corrección const
y, por extensión, romper estas garantías, el programador debe hacer esto intencionalmente (al igual que romper la encapsulación con Machiavelli
, arriba), y es probable que cause un comportamiento indefinido.
class DealBreaker : public ConstMemberFunctions {
public:
DealBreaker(int v = 0);
// A foreboding name, but it's const...
void no_guarantees() const;
}
DealBreaker::DealBreaker(int v /* = 0 */) : ConstMemberFunctions(v) {}
// Our assumption was incorrect.
// const_cast removes const-ness, making the compiler think we know what we're doing.
void DealBreaker::no_guarantees() const {
const_cast<DealBreaker*>(this)->set_val(823);
}
// ...
const DealBreaker d(50);
d.no_guarantees(); // Undefined behaviour: d really IS const, it may or may not be modified.
Sin embargo, debido a que esto requiera el programador de decir muy específicamente al compilador que tienen la intención de ignorar const
Ness, y ser consistentes entre los compiladores, generalmente es seguro asumir que const
código correcto se abstendrá de hacerlo a menos que se especifique lo contrario.