c++ - son - malas practicas de programacion
¿Las malas referencias de las variables miembro son malas prácticas? (3)
Hay varias razones por las que devolver referencias (o punteros) a las partes internas de una clase son malas. Comenzando por (lo que considero que es) lo más importante:
La encapsulación se infringe: se filtra un detalle de implementación, lo que significa que ya no se pueden modificar las partes internas de la clase como se desee. Si decidió no almacenar
first_
por ejemplo, pero para calcularlo sobre la marcha, ¿cómo le devolvería una referencia? No puedes, así estás atrapado.Invariantes ya no son sostenibles (en el caso de referencias no const): cualquiera puede acceder y modificar el atributo al que se hace referencia a voluntad, por lo tanto no puede "monitorear" sus cambios. Significa que no puede mantener una invariante de la cual este atributo es parte. Esencialmente, su clase se está convirtiendo en una burbuja.
Los problemas de por vida surgen: es fácil mantener una referencia o un puntero al atributo después de que el objeto original al que pertenecían dejó de existir. Esto es, por supuesto, un comportamiento indefinido. La mayoría de los compiladores intentarán advertir sobre el mantenimiento de referencias a objetos en la pila, por ejemplo, pero no conozco ningún compilador que haya podido generar tales advertencias para las referencias devueltas por funciones o métodos: usted está solo.
Como tal, generalmente es mejor no regalar referencias o punteros a los atributos. ¡Ni siquiera const!
Para valores pequeños, generalmente es suficiente pasarlos por copia (tanto in
como out
), especialmente ahora con semántica de movimiento (en el camino de entrada).
Para valores más grandes, realmente depende de la situación, a veces un Proxy puede aliviar sus problemas.
Finalmente, tenga en cuenta que para algunas clases, tener miembros públicos no es tan malo. ¿Cuál sería el sentido de encapsular a los miembros de un pair
? Cuando te encuentres escribiendo una clase que no es más que una colección de atributos (no invariante en absoluto), entonces, en lugar de obtener todos los OO sobre nosotros y escribir un par getter / setter para cada uno de ellos, considera hacerlos públicos.
Se dice que lo siguiente es mejor que tener primero / segundo como miembros públicos. Creo que esto es casi tan malo. Si está dando una forma de acceder a una variable privada fuera de la clase, ¿cuál es el punto? ¿No deberían las funciones ser
T First(); void(or T) First(const T&)
Muestra:
// Example 17-3(b): Proper encapsulation, initially with inline accessors. Later
// in life, these might grow into nontrivial functions if needed; if not, then not.
//
template<class T, class U>
class Couple {
Couple() : deleted_(false) { }
T& First() { return first_; }
U& Second() { return second_; }
void MarkDeleted() { deleted_ = true; }
bool IsDeleted() { return deleted_; }
private:
T first_;
U second_;
bool deleted_;
};
La respuesta depende de lo que uno está tratando de hacer. Las referencias recurrentes son una forma conveniente de facilitar la mutación de las estructuras de datos. Un buen ejemplo es el mapa stl. Devuelve referencia al elemento ie
std::map<int,std::string> a;
a[1] = 1;
nada que te impida hacer
auto & aref = a[1];
¿Es necesariamente una mala práctica? No lo pensaría así. Yo diría que si puedes prescindir, hazlo. Si le hace la vida más conveniente y eficiente, úselo y tenga en cuenta lo que está haciendo.
Si los tipos de template
T
y U
son estructuras grandes, entonces el retorno por valor es costoso . Sin embargo, tiene razón en que devolver por referencia equivale a dar acceso a una variable private
. Para resolver ambos problemas, const
referencias const
:
const T& First() const { return first_; }
const U& Second() const { return second_; }
PD. Además, es una mala práctica mantener las variables sin inicializar dentro del constructor, cuando no hay un método setter. Parece que en el código original, First()
y Second()
son wrappers sobre first_
y second_
que fueron diseñados para leer / escribir ambos.