virtuales sobrecarga operadores herencia funciones derivada constructores clases clase amigas c++ encapsulation

c++ - sobrecarga - ¿Cuándo debería preferir las funciones que no sean de no amigo que las funciones de miembro?



sobrecarga de funciones c++ (4)

Meyers mencionó en su libro Effective C ++ que, en ciertos escenarios, las funciones que no son miembros que no son amigos están mejor encapsuladas que las funciones miembro.

Ejemplo:

// Web browser allows to clear something class WebBrowser { public: ... void clearCache(); void clearHistory(); void removeCookies(); ... };

Muchos usuarios querrán realizar todas estas acciones juntos, por lo que WebBrowser también podría ofrecer una función para hacer precisamente eso:

class WebBrowser { public: ... void clearEverything(); // calls clearCache, clearHistory, removeCookies ... };

La otra forma es definir una función que no sea miembro no amiga.

void clearBrowser(WebBrowser& wb) { wb.clearCache(); wb.clearHistory(); wb.removeCookies(); }

La función de no miembro es mejor porque "no aumenta el número de funciones que pueden acceder a las partes privadas de la clase", lo que lleva a una mejor encapsulación.

Las funciones como clearBrowser son funciones de conveniencia porque no pueden ofrecer ninguna funcionalidad que un cliente de WebBrowser no pueda obtener de otra manera. Por ejemplo, si no existía clearBrowser , los clientes solo podrían llamar a clearCache , clearHistory y removeCookies .

Para mí, el ejemplo de las funciones de conveniencia es razonable. Pero, ¿hay algún otro ejemplo que no sea la función de conveniencia cuando la versión no miembro sobresale?

Más generalmente, ¿cuáles son las reglas de cuándo usar cuáles ?


Más generalmente, ¿cuáles son las reglas de cuándo usar cuáles?

Aquí están las reglas de Scott Meyer ( source ):

Scott tiene un interesante artículo impreso que defiende que las funciones que no son miembros que no son amigos mejoran la encapsulación de las clases. Utiliza el siguiente algoritmo para determinar dónde se coloca una función f:

if (f needs to be virtual) make f a member function of C; else if (f is operator>> or operator<<) { make f a non-member function; if (f needs access to non-public members of C) make f a friend of C; } else if (f needs type conversions on its left-most argument) { make f a non-member function; if (f needs access to non-public members of C) make f a friend of C; } else if (f can be implemented via C''s public interface) make f a non-member function; else make f a member function of C;

Su definición de encapsulación implica el número de funciones que se ven afectadas cuando se cambian los miembros de datos privados.

Lo que prácticamente lo resume todo, y es bastante razonable también, en mi opinión.


A menudo elijo crear métodos de utilidad fuera de mis clases cuando son específicos de la aplicación.

La aplicación generalmente se encuentra en un contexto diferente, luego los motores hacen el trabajo debajo. Si tomamos el ejemplo de un navegador web, los 3 métodos claros pertenecen al motor web, ya que es una funcionalidad necesaria que sería difícil de implementar en cualquier otro lugar. Sin embargo, ClearEverything () es definitivamente más específico de la aplicación. En este caso, su aplicación puede tener un pequeño cuadro de diálogo que tiene un botón de borrar todo para ayudar al usuario a ser más eficiente. Tal vez esto no sea algo que otra aplicación que reutilice el motor de su navegador web querría hacer y, por lo tanto, tenerlo en la clase de motor sería más desordenado.

Otro ejemplo es un en bibliotecas matemáticas. A menudo tiene sentido tener una funcionalidad más avanzada como valor medio o derivación estándar implementada como parte de una clase matemática. Sin embargo, si tiene una forma específica de aplicación para calcular algún tipo de medio que no sea la versión estándar, probablemente debería estar fuera de su clase y ser parte de una biblioteca de utilidades específica para su aplicación.

Nunca he sido un gran fanático de las reglas sólidas codificadas para implementar las cosas de una manera u otra, a menudo es una cuestión de ideología y principios.

METRO.


La ubicación y permitir que la clase proporcione características ''suficientes'' mientras se mantiene la encapsulación son algunas cosas a considerar.

Si WebBrowser se reutiliza en muchos lugares, las dependencias / clientes pueden definir múltiples funciones de conveniencia. Esto mantiene sus clases ( WebBrowser ) ligeras y fáciles de administrar.

Lo inverso sería que el WebBrowser termina complaciendo a todos los clientes, y solo se convierte en una bestia monolítica que es difícil de cambiar.

¿Encuentra que la clase carece de funcionalidad una vez que se ha puesto en uso en múltiples escenarios? ¿Surgen patrones en tus funciones de conveniencia? Es mejor (IMO) diferir formalmente la interfaz de la clase hasta que surjan patrones y haya una buena razón para agregar esta funcionalidad. Una clase mínima es más fácil de mantener, pero no desea implementaciones redundantes en todas partes porque eso lleva la carga de mantenimiento a sus clientes.

Si sus funciones de conveniencia son complejas de implementar, o hay un caso común que puede mejorar el rendimiento significativamente (por ejemplo, para vaciar una colección segura de subprocesos con un bloqueo, en lugar de un elemento a la vez con un bloqueo cada vez), también puede Quiero considerar ese caso.

También habrá casos en los que se dé cuenta de que algo falta realmente en el WebBrowser medida que lo usa.


Las funciones que no son miembros se usan comúnmente cuando el desarrollador de una biblioteca quiere escribir operadores binarios que puedan sobrecargarse en cualquier argumento con un tipo de clase, ya que si los hace miembros de la clase solo puede sobrecargar el segundo argumento (el primero es implícitamente un objeto de esa clase). Los diversos operadores aritméticos para complex son quizás el ejemplo definitivo para esto.

En el ejemplo que cita, la motivación es de otro tipo: use el diseño menos acoplado que aún le permita hacer el trabajo .

Esto significa que si bien está clearEverything que clearEverything puede ser clearEverything (y, para ser franco, podría ser) miembro, no lo hacemos porque técnicamente no tiene que serlo. Esto te compra dos cosas:

  1. No tiene que aceptar la responsabilidad de tener un método clearEverything en su interfaz pública (una vez que envía uno, está casado con él de por vida).
  2. El número de funciones con acceso a los miembros private de la clase es uno más bajo, por lo tanto, cualquier cambio en el futuro será más fácil de realizar y será menos probable que cause errores.

Dicho esto, en este ejemplo en particular, siento que el concepto está siendo llevado demasiado lejos, y para una función tan "inocente" gravitaría para convertirlo en un miembro. Pero el concepto es sólido, y en escenarios del "mundo real" donde las cosas no son tan simples, tendría mucho más sentido.