¿Cómo elimino la duplicación de código entre funciones miembro constantes y no constantes similares?
Digamos que tengo lo siguiente class X
donde quiero devolver el acceso a un miembro interno:
class Z
{
// details
};
class X
{
std::vector<Z> vecZ;
public:
Z& Z(size_t index)
{
// massive amounts of code for validating index
Z& ret = vecZ[index];
// even more code for determining that the Z instance
// at index is *exactly* the right sort of Z (a process
// which involves calculating leap years in which
// religious holidays fall on Tuesdays for
// the next thousand years or so)
return ret;
}
const Z& Z(size_t index) const
{
// identical to non-const X::Z(), except printed in
// a lighter shade of gray since
// we're running low on toner by this point
}
};
Las dos funciones miembro X::Z()
tienen X::Z() const
código idéntico dentro de las llaves. Este es código duplicado y puede causar problemas de mantenimiento para funciones largas con lógica compleja .
¿Hay alguna manera de evitar la duplicación de este código?
Para obtener una explicación detallada, consulte el título "Evitar la duplicación en funciones de miembros const
y no const
miembros", en la pág. 23, en el elemento 3 "Utilizar const
siempre que sea posible", en Effective C++ , edición 3D de Scott Meyers, ISBN-13: 9780321334879.
Aquí está la solución de Meyers (simplificada):
struct C {
const char & get() const {
return c;
}
char & get() {
return const_cast<char &>(static_cast<const C &>(*this).get());
}
char c;
};
Las dos conversiones y la llamada a función pueden ser feas, pero son correctas en un const
método que no es un método, ya que eso implica que el objeto no era const
para empezar. (Meyers tiene una discusión exhaustiva sobre esto).
C++17 ha actualizado la mejor respuesta para esta pregunta:
T const & f() const {
return something_complicated();
}
T & f() {
return const_cast<T &>(std::as_const(*this).f());
}
Esto tiene las ventajas de que:
- Es obvio lo que esta pasando
- Tiene una sobrecarga de código mínima: cabe en una sola línea
- Es difícil equivocarse (sólo se puede descartar
volatile
por accidente, perovolatile
es un calificador poco común)
Si desea seguir la ruta de deducción completa, puede lograrlo teniendo una función de ayuda.
template<typename T>
constexpr T & as_mutable(T const & value) noexcept {
return const_cast<T &>(value);
}
template<typename T>
constexpr T * as_mutable(T const * value) noexcept {
return const_cast<T *>(value);
}
template<typename T>
constexpr T * as_mutable(T * value) noexcept {
return value;
}
template<typename T>
void as_mutable(T const &&) = delete;
Ahora ni siquiera puedes equivocarte volatile
y el uso parece
decltype(auto) f() const {
return something_complicated();
}
decltype(auto) f() {
return as_mutable(std::as_const(*this).f());
}