Para evitar la duplicación no trivial relacionada con const de C ++, ¿hay casos en que const_cast funcionaría pero una función const privada que devuelve no const no funcionaría?
En el artículo 3 de C ++ efectivo de Scott Meyers , sugiere que un const_cast combinado con un reparto estático puede ser una forma efectiva y segura de evitar el código duplicado, por ejemplo
const void* Bar::bar(int i) const
{
...
return variableResultingFromNonTrivialDotDotDotCode;
}
void* Bar::bar(int i)
{
return const_cast<void*>(static_cast<const Bar*>(this)->bar(i));
}
Meyers continúa explicando que hacer que la función const llame a la función no const es peligroso.
El siguiente código es un contraejemplo que muestra:
- Contrariamente a la sugerencia de Meyers, a veces el const_cast combinado con un reparto estático es peligroso.
- a veces hacer que la función const llame al no const es menos peligroso
- a veces, las dos formas de usar const_cast ocultan errores de compilación potencialmente útiles
- evitar una const_cast y tener un miembro privado const adicional que devuelva una no const es otra opción
¿Alguna de las estrategias const_cast para evitar la duplicación de código se considera una buena práctica? ¿Preferirías la estrategia de método privado en su lugar? ¿Hay casos en los que const_cast funcionaría pero un método privado no? ¿Hay otras opciones (además de la duplicación)?
Mi preocupación con las estrategias const_cast es que incluso si el código es correcto cuando se escribe, más adelante durante el mantenimiento, el código podría volverse incorrecto y const_cast ocultaría un error útil del compilador. Parece que una función privada común es generalmente más segura.
class Foo
{
public:
Foo(const LongLived& constLongLived, LongLived& mutableLongLived)
: mConstLongLived(constLongLived), mMutableLongLived(mutableLongLived)
{}
// case A: we shouldn't ever be allowed to return a non-const reference to something we only have a const reference to
// const_cast prevents a useful compiler error
const LongLived& GetA1() const { return mConstLongLived; }
LongLived& GetA1()
{
return const_cast<LongLived&>( static_cast<const Foo*>(this)->GetA1() );
}
/* gives useful compiler error
LongLived& GetA2()
{
return mConstLongLived; // error: invalid initialization of reference of type 'LongLived&' from expression of type 'const LongLived'
}
const LongLived& GetA2() const { return const_cast<Foo*>(this)->GetA2(); }
*/
// case B: imagine we are using the convention that const means thread-safe, and we would prefer to re-calculate than lock the cache, then GetB0 might be correct:
int GetB0(int i) { return mCache.Nth(i); }
int GetB0(int i) const { return Fibonachi().Nth(i); }
/* gives useful compiler error
int GetB1(int i) const { return mCache.Nth(i); } // error: passing 'const Fibonachi' as 'this' argument of 'int Fibonachi::Nth(int)' discards qualifiers
int GetB1(int i)
{
return static_cast<const Foo*>(this)->GetB1(i);
}*/
// const_cast prevents a useful compiler error
int GetB2(int i) { return mCache.Nth(i); }
int GetB2(int i) const { return const_cast<Foo*>(this)->GetB2(i); }
// case C: calling a private const member that returns non-const seems like generally the way to go
LongLived& GetC1() { return GetC1Private(); }
const LongLived& GetC1() const { return GetC1Private(); }
private:
LongLived& GetC1Private() const { /* pretend a bunch of lines of code instead of just returning a single variable*/ return mMutableLongLived; }
const LongLived& mConstLongLived;
LongLived& mMutableLongLived;
Fibonachi mCache;
};
class Fibonachi
{
public:
Fibonachi()
{
mCache.push_back(0);
mCache.push_back(1);
}
int Nth(int n)
{
for (int i=mCache.size(); i <= n; ++i)
{
mCache.push_back(mCache[i-1] + mCache[i-2]);
}
return mCache[n];
}
int Nth(int n) const
{
return n < mCache.size() ? mCache[n] : -1;
}
private:
std::vector<int> mCache;
};
class LongLived {};
Respuestas:
Al implementar funciones miembro const y no const que solo difieren en si el ptr / referencia devuelto es const, la mejor estrategia DRY es:
p.ej
Llamemos a esto la función const privada que devuelve un patrón no const .
Esta es la mejor estrategia para evitar duplicaciones de una manera directa y al mismo tiempo permitir que el compilador realice comprobaciones potencialmente útiles e informe mensajes de error relacionados con el constante.
fuente
const
instancia (a menos que la referencia sea a algo declaradomutable
, o a menos que emplee unconst_cast
pero en ambos casos no hay ningún problema para empezar ) También pude encontrar nada en la "función private const regresar patrón no constante" (si es que se pretende que sea una broma llamarlo patrón .... isnt divertido;)Sí, tiene razón: muchos programas de C ++ que intentan la corrección constante están en clara violación del principio DRY, e incluso el miembro privado que regresa sin constante es un poco demasiado complejo para su comodidad.
Sin embargo, se pierde una observación: la duplicación de código debido a la corrección constante solo es un problema si está dando acceso a otro código a sus miembros de datos. Esto en sí mismo es una violación de la encapsulación. En general, este tipo de duplicación de código ocurre principalmente en accesores simples (después de todo, le está dando acceso a miembros ya existentes, el valor de retorno generalmente no es el resultado de un cálculo).
Mi experiencia es que las buenas abstracciones no tienden a incluir accesores. En consecuencia, evito en gran medida este problema definiendo funciones miembro que realmente hacen algo, en lugar de simplemente proporcionar acceso a los miembros de datos; Intento modelar el comportamiento en lugar de los datos. Mi intención principal en esto es obtener algo de abstracción tanto de mis clases como de sus funciones miembro individuales, en lugar de simplemente usar mis objetos como contenedores de datos. Pero este estilo también es bastante exitoso al evitar las toneladas de accesos de una línea repetitivos constantes / no constantes que son tan comunes en la mayoría de los códigos.
fuente