una resueltos programacion poo orientada objetos miembros metodos ejercicios ejemplos ejemplo constructores codigo clases clase c++ encapsulation member-functions non-member-functions effective-c++

resueltos - metodos en c++



Artículo C++ eficaz 23 Prefiere funciones no miembro no miembro a funciones miembro (7)

Mientras desconcertaba con algunos hechos sobre el diseño de clases, específicamente si las funciones deberían ser miembros o no, busqué en Effective c ++ y encontré el Ítem 23, es decir, prefiero las funciones no miembro no miembro a las funciones miembro. Leerlo de primera mano con el ejemplo del navegador web tiene cierto sentido, sin embargo, las funciones de conveniencia (llamadas funciones no miembro como esta en el libro) en ese ejemplo cambian el estado de la clase, ¿verdad?

  • Entonces, primera pregunta, ¿no deberían ser miembros?

  • Leyendo un poco más, considera que las funciones STL y, de hecho, algunas funciones que no están implementadas por algunas clases se implementan en stl. Siguiendo las ideas del libro, evolucionan hacia algunas funciones de conveniencia que se empaquetan en algunos espacios de nombres razonables, como std::sort , std::copy from algorithm . Por ejemplo, vector clase vector no tiene una función de sort y una usa la función de sort stl, por lo que no es miembro de la clase vectorial. Pero también se podría estirar el mismo razonamiento a algunas otras funciones en la clase vectorial, como assign por lo que tampoco podría implementarse como miembro sino como una función de conveniencia. Sin embargo, eso también cambia el estado interno del objeto, como el género en el que operaba. Entonces, ¿cuál es la razón de ser de este tema sutil pero importante (supongo)?

Si tiene acceso al libro, ¿puede aclarar un poco más estos puntos para mí?


Entonces, primera pregunta, ¿no deberían ser miembros?

No, esto no sigue. En el diseño idiomático de clase de C ++ (al menos, en las expresiones idiomáticas utilizadas en Effective C ++ ), las funciones de no amigo no miembro extienden la interfaz de clase. Se pueden considerar parte de la API pública para la clase, a pesar de que no necesitan y no tienen acceso privado a la clase. Si este diseño no es "OOP" por alguna definición de OOP entonces, OK, C ++ idiomático no es OOP por esa definición.

estirar el mismo razonamiento a algunas otras funciones en la clase vectorial

Es cierto, hay algunas funciones miembro de contenedores estándar que podrían haber sido funciones gratuitas. Por ejemplo, vector::push_back se define en términos de insert , y ciertamente podría implementarse sin acceso privado a la clase. Sin embargo, en ese caso, push_back es parte de un concepto abstracto, BackInsertionSequence , ese vector se implementa. Tales conceptos genéricos abarcan el diseño de clases particulares, por lo tanto, si está diseñando o implementando sus propios conceptos genéricos que podrían influir en dónde coloca las funciones.

Ciertamente, hay partes del estándar que posiblemente deberían haber sido diferentes, por ejemplo, std :: string tiene demasiadas funciones miembro . Pero lo que está hecho está hecho, y estas clases fueron diseñadas antes de que la gente realmente se estableciera en lo que ahora podríamos llamar el estilo moderno de C ++. La clase funciona de cualquier manera, por lo que hay mucho beneficio práctico que puedes obtener al preocuparte por la diferencia.


Creo que la razón de esta regla es que al usar las funciones miembro puede confiar demasiado en las partes internas de una clase por accidente. Cambiar el estado de una clase no es un problema. El verdadero problema es la cantidad de código que necesita cambiar si modifica alguna propiedad privada dentro de su clase. Mantener la interfaz de la clase (métodos públicos) lo más pequeña posible reduce tanto la cantidad de trabajo que tendrá que hacer en ese caso como el riesgo de hacer algo extraño con sus datos privados, dejándolo con una instancia en un estado inconsistente .

AtoMerZ también tiene razón, las funciones de no amigos que no son miembros también se pueden modelar y reutilizar para otros tipos.

Por cierto, debes comprar tu copia de Effective C ++, es un gran libro, pero no intentes cumplir siempre con cada elemento de este libro. Diseño orientado a objetos, buenas prácticas (de libros, etc.) Y experiencia (creo que también está escrito en C ++ efectivo en alguna parte).


Creo que sort no está implementado como una función miembro porque se usa ampliamente, no solo para vectores. Si lo tenían como función de miembro, tendrían que volver a implementarlo cada vez que lo usara cada contenedor. Entonces creo que es para una implementación más fácil.


El acceso al libro no es necesario.

Los problemas que estamos tratando aquí son Dependencia y Reutilización .

En un software bien diseñado, intenta aislar los elementos entre sí para reducir las Dependencias, porque las Dependencias son un obstáculo que hay que superar cuando el cambio es necesario.

En un software bien diseñado, aplica el principio DRY (No repetir) porque cuando es necesario un cambio, es doloroso y propenso a errores tener que repetirlo en una docena de lugares diferentes.

La mentalidad de OO "clásica" es cada vez peor en el manejo de dependencias. Al tener muchos y muchos métodos que dependen directamente de las partes internas de la clase, el más mínimo cambio implica una reescritura completa. No es necesario que sea así.

En C ++, el STL (no toda la biblioteca estándar) se diseñó con los objetivos explícitos de:

  • cortar dependencias
  • permitiendo la reutilización

Por lo tanto, los contenedores exponen interfaces bien definidas que ocultan sus representaciones internas pero aún ofrecen suficiente acceso a la información que encapsulan para que los algoritmos se puedan ejecutar en ellas. Todas las modificaciones se realizan a través de la interfaz del contenedor para que las invariantes estén garantizadas.

Por ejemplo, si piensa en los requisitos del algoritmo de sort . Para la implementación utilizada (en general) por el STL, requiere (del contenedor):

  • acceso eficiente a un elemento en un índice dado: acceso aleatorio
  • la capacidad de intercambiar dos elementos: no asociativo

Por lo tanto, cualquier contenedor que proporcione acceso aleatorio y no sea asociativo es (en teoría) adecuado para clasificarse de manera eficiente mediante (digamos) un algoritmo de clasificación rápida.

¿Cuáles son los contenedores en C ++ que satisfacen esto?

  • la matriz C básica
  • deque
  • vector

Y cualquier contenedor que pueda escribir si presta atención a estos detalles.

Sería un desperdicio, ¿no ?, reescribir (copiar / pegar / ajustar) el sort para cada uno de esos?

Tenga en cuenta, por ejemplo, que hay un método std::list::sort . Por qué ? Debido a que std::list no ofrece acceso aleatorio (informalmente myList[4] no funciona), por lo tanto, el algoritmo de sort no es adecuado.


El criterio que uso es si una función se podría implementar de manera significativamente más eficiente al ser una función miembro, entonces debería ser una función miembro. ::std::sort no cumple esa definición. De hecho, no existe una diferencia de eficiencia en la implementación externa o interna.

Una gran mejora de eficiencia al implementar algo como función de miembro (o amigo) significa que se beneficia enormemente de conocer el estado interno de la clase.

Parte del arte del diseño de interfaz es el arte de encontrar el conjunto mínimo de funciones miembro, de forma que todas las operaciones que desee realizar en el objeto se puedan implementar de manera razonablemente eficiente en términos de ellas. Y este conjunto no debería admitir operaciones que no deberían realizarse en la clase. Entonces no puedes simplemente implementar un montón de funciones getter y setter y llamarlo bueno.


La motivación es simple: mantener una sintaxis consistente. A medida que la clase evoluciona o se usa, aparecerán varias funciones de conveniencia que no son miembros; no desea modificar la interfaz de clase para agregar algo como toUpper a una clase de cadena, por ejemplo. (En el caso de std::string , por supuesto, no puedes). La preocupación de Scott es que cuando esto sucede, terminas con una sintaxis inconsistente:

s.insert( "abc" ); toUpper( s );

Al usar solo funciones gratuitas, declararlas como amigo según sea necesario, todas las funciones tienen la misma sintaxis. La alternativa sería modificar la definición de clase cada vez que agregue una función de conveniencia.

No estoy completamente convencido. Si una clase está bien diseñada, tiene una funcionalidad básica, es claro para el usuario qué funciones son parte de esa funcionalidad básica, y cuáles son funciones de conveniencia adicionales (si las hay). Globalmente, string es una especie de caso especial, ya que está diseñado para ser utilizado para resolver muchos problemas diferentes; No me puedo imaginar que este sea el caso para muchas clases.


Varios pensamientos:

  • Es agradable cuando los no miembros trabajan a través de la API pública de la clase, ya que reduce la cantidad de código que:
    • necesita ser monitoreado cuidadosamente para asegurar invariantes de clase,
    • debe cambiarse si se rediseña la implementación del objeto.
  • Cuando eso no es lo suficientemente bueno, un no miembro aún puede ser un friend .
  • Escribir una función que no sea miembro suele ser una tarea menos conveniente, ya que los miembros no tienen un alcance implícito, PERO si se tiene en cuenta la evolución del programa:
    • Una vez que existe una función no miembro y se sabe que la misma funcionalidad sería útil para otros tipos, generalmente es muy fácil convertir la función en una plantilla y tenerla disponible no solo para ambos tipos, sino también para futuros tipos arbitrarios. Dicho de otra manera, las plantillas que no son miembros permiten una reutilización de algoritmo aún más flexible que el polimorfismo / despacho virtual en tiempo de ejecución: las plantillas permiten algo conocido como tipado de pato .
    • Un tipo existente con una función de miembro útil fomenta cortar y pegar a los otros tipos que deseen un comportamiento análogo porque la mayoría de las formas de convertir la función para su reutilización requieren que cada acceso miembro implícito se haga un acceso explícito en un objeto particular, que será un tedius más de 30 segundos para el programador ...
  • Las funciones miembro permiten la object.function(x, y, z) , que en mi humilde opinión es muy conveniente, expresiva e intuitiva. También funcionan mejor con funciones de descubrimiento / finalización en muchos IDE.
  • Una separación como funciones miembro y no miembro puede ayudar a comunicar la naturaleza esencial de la clase, sus invariantes y operaciones fundamentales, y agrupar de forma lógica las funciones complementarias y posiblemente ad-hoc de "conveniencia". Considera la sabiduría de Tony Hoare:

    "Hay dos formas de construir un diseño de software: una forma es hacerlo tan simple que obviamente no hay deficiencias, y la otra es hacerlo tan complicado que no haya deficiencias obvias. El primer método es mucho más difícil. "

    • Aquí, el uso de no miembros no es necesariamente mucho más difícil, pero debe pensar más acerca de cómo está accediendo a los datos de los miembros y los métodos privados / protegidos y por qué, y qué operaciones son fundamentales. Tal búsqueda del alma mejoraría el diseño con funciones de miembros también, es más fácil ser perezoso acerca de: - /.
  • A medida que la funcionalidad no miembro se expande en sofisticación o recoge dependencias adicionales, las funciones se pueden mover a encabezados y archivos de implementación separados, incluso bibliotecas, para que los usuarios de la funcionalidad principal solo "paguen" por usar las piezas que desean.

(La respuesta de Omnifarious es obligatoria, tres veces si es nueva para ti).