2011-10-19 17 views
15

Meyers mencionó en su libro Effective C++ que, en ciertos escenarios, las funciones de no amigos no miembros están mejor encapsuladas que las funciones de miembros.¿Cuándo debería preferir las funciones de no amigo no miembro a las funciones de 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 en conjunto, por lo 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 no miembro de la función no amigo.

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

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

Funciones como clearBrowser son funciones de confort porque no pueden ofrecer ninguna funcionalidad WebBrowser un cliente no podía conseguir ya de alguna otra manera. Por ejemplo, si clearBrowser no existe, los clientes pueden simplemente llamar al 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 en general, ¿Cuáles son las reglas de cuándo usar qué?

+4

Creo que hay más religión que reglas objetivas reales para ese tema. – PlasmaHH

+1

Cualquier tipo de algoritmo se beneficia de ser gratuito en lugar de miembros, ya que puede reutilizarlos para una gama más amplia de objetos. Un ejemplo de esto son las funciones gratuitas 'begin()'/'end()' que se agregaron al nuevo estándar (que le permiten iterar matrices estáticas y contenedores). –

+3

@PlasmaHH: No creo que sea más como religión. El fundamento para hacer algunas funciones de no miembro y no amigo es bastante razonable. – Nawaz

Respuesta

20

Más en general, ¿Cuáles son las reglas de cuándo usar qué?

Esto es lo que las reglas de Scott Meyer son (source):

Scott tiene un interesante artículo en la impresión que aboga que las funciones no-amigo no miembros a mejorar la encapsulación para las clases. Se utiliza el siguiente algoritmo para determinar donde una función f se coloca:

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 afectados cuando se cambian los datos privados miembros.

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

0

Las funciones no miembro se usan comúnmente cuando el desarrollador de una biblioteca quiere escribir operadores binarios que pueden sobrecargarse en cualquier argumento con un tipo de clase, ya que si los convierte en miembros de la clase solo puede sobrecargar el segundo argumento (el primero es implícitamente un objeto de esa clase). El various arithmetic operators para complex es quizás el ejemplo definitivo para esto.

En el ejemplo que cita, la motivación es de otro tipo: utiliza el diseño menos acoplado que todavía le permite hacer el trabajo.

Esto significa que si bien clearEverything podría (y, para ser sincero, sería bastante probable) ser miembro, no lo hacemos porque técnicamente no tiene que serlo. Esto le compra dos cosas:

  1. Usted no tiene que aceptar la responsabilidad de tener un método clearEverything en su interfaz pública (una vez que se envía con uno, que estés casado con ella 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 menos probable que cause errores.

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

3

A menudo elijo construir 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 al de los motores que realizan 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, el 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 tenga un botón de borrar para ayudar al usuario a ser más eficiente. Tal vez esto no sea algo que otra aplicación reutilice el motor de su navegador web y, por lo tanto, tenerlo en la clase de motor sería más desorden.

Otro ejemplo es una biblioteca matemática. 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 media que no sea la versión estándar, probablemente debería estar fuera de su clase y formar parte de una biblioteca de utilidad específica para su aplicación.

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

M.

0

Localidad y permitiendo que la clase para proporcionar 'suficiente' funciones, manteniendo una 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) livianas y fáciles de administrar.

Lo contrario sería que el WebBrowser termina por complacer a todos los clientes, y simplemente se convierte en una bestia monolítica que es difícil de cambiar.

¿Le parece que la clase carece de funcionalidad una vez que ha sido utilizada en múltiples escenarios? ¿Surgen patrones en sus funciones de conveniencia? Lo mejor (IMO) es diferir la extensión formal de 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 aumenta la carga de mantenimiento de sus clientes.

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

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

Cuestiones relacionadas