Elemento 23 de C++ Efectivo Preferir funciones no miembro no amigo a funciones miembro


Mientras estaba desconcertando con algunos hechos sobre el diseño de clases, específicamente si las funciones deberían ser miembros o no, busqué en c++ Efectivo y encontré el Ítem 23, a saber, Preferir funciones no-miembro no-amigo a funciones miembro. Leer eso de primera mano con el ejemplo del navegador web tenía algún sentido, sin embargo, las funciones de conveniencia( llamadas las funciones no miembros como esta en el libro) en ese ejemplo cambian el estado de la clase, ¿no?

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

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

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

Author: Umut Tabak, 2011-05-13

7 answers

El acceso al libro no es necesario.

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

En un software bien diseñado, intenta aislar los elementos unos de otros para reducir las Dependencias, porque las dependencias son un obstáculo que hay que superar cuando se necesita un cambio.

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

La mentalidad OO "clásica" es cada vez más mala para manejar dependencias. Al tener muchos y muchos métodos que dependen directamente de los componentes internos de la clase, el más mínimo cambio implica una reescritura completa. No tiene por qué ser así.

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

  • cortar dependencias
  • permitir la reutilización

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

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

  • acceso eficiente a una item at a given index: Random Access
  • la capacidad de intercambiar dos elementos: no asociativo

Por lo tanto, cualquier contenedor que proporciona Acceso Aleatorio y no es Asociativo es (en teoría) adecuado para ser ordenado eficientemente por (digamos) un algoritmo de Ordenación Rápida.

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

  • el array C básico
  • deque
  • vector

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

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

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 sort from no es adecuado.

 32
Author: Matthieu M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-13 14:35:53

El criterio que utilizo es si una función podría implementarse 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 hay ninguna diferencia de eficiencia en la implementación externa frente a la interna.

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

Parte de la el arte del diseño de interfaz es el arte de encontrar el conjunto más mínimo de funciones miembro de tal manera que todas las operaciones que desee realizar en el objeto puedan implementarse de manera razonablemente eficiente en términos de ellas. Y este conjunto no debe soportar operaciones que no se deben realizar en la clase. Así que no puedes simplemente implementar un montón de funciones getter y setter y llamarlo bueno.

 18
Author: Omnifarious,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-13 09:36:01

Creo que la razón de esta regla es que al usar funciones miembro puede confiar demasiado en las funciones 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 tal caso como el riesgo de hacer algo extraño con su privado datos, dejándole con una instancia en un estado inconsistente.

AtoMerZ también tiene razón, las funciones no-miembro no-amigo pueden ser templadas y reutilizadas para otros tipos también.

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

 11
Author: ascobol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-13 10:03:12

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

No, esto no sigue. En el diseño de clases idiomáticas de C++ (al menos, en los modismos utilizados en C++efectivo), las funciones no-miembro no-amigo extienden la interfaz de clase. Pueden ser considerados parte de la API pública para la clase, a pesar del hecho 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, idiomatic C++ no es OOP por esa definición.

Estirar el mismo razonamiento a algunos otras funciones en la clase vectorial

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

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

 3
Author: Steve Jessop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-13 10:38:31

La motivación es simple: mantener una sintaxis consistente. Como la clase evoluciona o se utiliza, varias funciones de conveniencia no miembro no desea modificar la interfaz de clase para agregar algo como toUpper a una clase string, por ejemplo. (En el caso de std::string, por supuesto, no se puede.) La preocupación de Scott es que cuando esto sucede, usted termina con sintaxis inconsistente:

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

Usando solo funciones libres, declarándolas amigas según sea necesario, todo las funciones tienen la misma sintaxis. La alternativa sería modificar la definición de clase cada vez que se agrega una función de conveniencia.

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

 2
Author: James Kanze,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-13 09:42:33

Varios pensamientos:

  • Es bueno 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 cuidadosamente monitoreado para asegurar invariantes de clase,
    • necesita ser cambiado si la implementación del objeto es rediseñada.
  • Cuando eso no es lo suficientemente bueno, un no miembro todavía se puede hacer un friend.
  • Escribir una función no miembro suele ser menos conveniente, ya que los miembros no están implícitamente en alcance, PERO si se considera la evolución del programa:
    • Una vez que existe una función no miembro y se da cuenta de 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 tipos futuros arbitrarios. Dicho de otra manera, las plantillas no miembros permiten una reutilización del algoritmo aún más flexible que el polimorfismo en tiempo de ejecución / envío virtual: las plantillas permiten algo conocido como pato escribiendo .
    • Un tipo existente que tiene una función miembro útil anima a cortar y pegar a los otros tipos que desean un comportamiento análogo porque la mayoría de las formas de convertir la función para su reutilización requieren que cada acceso implícito a un miembro se convierta en un acceso explícito a un objeto en particular, lo que va a ser un tedio más de 30 segundos para el programador....
  • Las funciones miembro permiten la notación object.function(x, y, z), que en mi humilde opinión es muy conveniente, expresiva y intuitivo. También funcionan mejor con características 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 lógicamente agrupar las características adicionales y posiblemente ad-hoc de "conveniencia". Considere la sabiduría de Tony Hoare:

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

    • Aquí, el uso de los no miembros no es necesariamente mucho más difícil, pero tiene que pensar más en 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 de conciencia mejoraría el diseño con las funciones de miembro también, es más fácil ser perezoso sobre : -/.
  • As 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, por lo que los usuarios de la funcionalidad principal solo "pagan" por usar las partes que desean.

(La respuesta de Omnifarious es una lectura obligada, tres veces si es nueva para ti.)

 2
Author: Tony Delroy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-17 07:30:12

Creo que sort no se implementa como una función miembro porque es ampliamente utilizada, no solo para vectores. Si la tuvieran como función miembro, tendrían que volver a implementarla cada vez para cada contenedor que la use. Así que creo que es para facilitar la implementación.

 1
Author: atoMerz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-13 09:21:18