varias una simple programacion polimorfismo orientada objetos miembros herencia funciones funcion ejemplos clases clase amigas c++ function

c++ - simple - miembros de una clase en programacion orientada a objetos



Cómo las funciones que no son miembros mejoran la encapsulación (8)

Leí el article Scott Meyers sobre el tema y me confundí bastante sobre lo que está hablando. Tengo 3 preguntas aquí.

Pregunta 1

Para explicar en detalle, suponga que estoy escribiendo una clase vector<T> simple con métodos como push_back , insert y operator [] . Si siguiera el algoritmo de Meyers, terminaría con todas las funciones de amigos no miembros. Tendré una clase vectorial con pocos miembros privados y muchas funciones de amigos no miembros. ¿Es esto de lo que está hablando?

Pregunta 2

Todavía no entiendo cómo las funciones que no son miembros mejoran la encapsulación. Considere el código dado en el artículo de Meyers.

class Point { public: int getXValue() const; int getYValue() const; void setXValue(int newXValue); void setYValue(int newYValue); private: ... // whatever... };

Si se sigue su algoritmo, los métodos setXXXX deberían ser no miembros. Mi pregunta es ¿cómo aumenta la encapsulación? El tambien dice

Ahora hemos visto que una forma razonable de medir la cantidad de encapsulación en una clase es contar el número de funciones que podrían estar dañadas si cambia la implementación de la clase.

Hasta que no mantengamos intacta la firma del método cuando cambie la implementación de la clase, ningún código de cliente se romperá y está bien encapsulado, ¿verdad? Lo mismo se aplica a las funciones no miembros también. Entonces, ¿cuál es la ventaja que proporciona la función no miembro?

Pregunta 3

Citando su algoritmo

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; }

¿Qué quiso decir con f necesita conversiones de tipo en su argumento más a la izquierda ? También dice lo siguiente en el artículo.

Además, ahora vemos que la afirmación común de que "las funciones de amigo violan la encapsulación" no es del todo cierta. Los amigos no violan la encapsulación, solo la reducen, exactamente de la misma manera que las funciones de un miembro.

Esto y el algoritmo anterior son contradictorios, ¿verdad?


Hasta que no mantengamos intacta la firma del método cuando cambie la implementación de la clase, ningún código de cliente se romperá y está bien encapsulado, ¿verdad? Lo mismo se aplica a las funciones no miembros también. Entonces, ¿cuál es la ventaja que proporciona la función no miembro?

Meyers dice que una clase con muchos métodos está menos encapsulada que una clase con menos métodos, porque las implementaciones de todos esos métodos internos están sujetas a cambios. Si alguno de los métodos hubiera sido no miembros, se reduciría la cantidad de métodos que podrían verse afectados por los cambios internos de la clase.

¿Qué quiso decir con f necesita conversiones de tipo en su argumento más a la izquierda?

Creo que se está refiriendo a operadores, funciones que tendrían un argumento implícito del extremo izquierdo si fueran funciones miembro.


De los cuatro casos que proporciona para hacer funciones no miembros, lo más cercano a los métodos vector propuestos es este:

else if (f can be implemented via C''s public interface) make f a non-member function;

Pero no puede implementar métodos como push_back , insert u operator[] través de una interfaz pública. Esos son la interfaz pública. Podría ser posible implementar push_back en términos de insert , pero en gran medida, ¿qué interfaz pública va a usar para tales métodos?

Además, los casos para dar amistad a funciones que no son miembros son realmente casos especiales, como lo veo, el operator<< y el operator>> , y las conversiones de tipo, requieren datos muy precisos y no filtrados de la clase. Estos métodos son naturalmente muy invasivos.

Si bien no soy un fanático del Dr. Dobbs, ni de ninguno de los "gurús de C ++" que se reivindican, creo que en este caso es posible que tenga que adivinar dos veces su propia implementación. El algoritmo de Scott Meyer me parece razonable.


Dice específicamente "funciones que no son miembros que no son amigos " (énfasis mío). Si necesitaría convertir a la función que no es miembro en un demonio, sus algoritmos dicen que debería ser una función miembro a menos que sea el operador >> o el operador << o necesite conversiones de tipo en su argumento más a la izquierda.


Echa un vistazo a los algoritmos STL. sort , copy , transform , etc. operan en iteradores y no son funciones miembro.

También estás equivocado acerca de su algoritmo. Las funciones set y get no se pueden implementar con la interfaz pública de Point.


El significado f necesita conversiones de tipo en el argumento más a la izquierda es el siguiente:

Considera el siguiente senario:

Class Integer { private: int num; public: int getNum( return num;) Integer(int n = 0) { num = n;} Integer(const Integer &rhs)) { num = rhs.num ;} Integer operator * (const Integer &rhs) { return Integer(num * rhs.num); } } int main() { Integer i1(5); Integer i3 = i1 * 3; // valid Integer i3 = 3 * i1 ; // error }

En el código anterior, i3 = i1 * 3 es equivalente a this->operator*(3) que es válido ya que 3 se convierte implícitamente a entero.

Donde, como en el caso posterior, i3 = 3 * i1 es equivalente a 3.operator*(i1) , según la regla cuando el operador de sobrecarga u utiliza la función miembro, el objeto invocante debe ser de la misma clase. pero aquí no es eso.

Para hacer que el Integer i3 = 3 * i1 funcione, se puede definir la función no miembro de la siguiente manera:

Integer operator * (const Integer &lhs , const Integer &rhs) // non-member function { return Integer(lhs.getNum() * rhs.getNum()); }

Creo que obtendrás una idea de este ejemplo ...


Pregunta 2

Scott Meyers también ha sugerido lo siguiente si te acuerdas:

-> Mantener la interfaz de clase completa y mínima.

Vea el siguiente escenario:

class Person { private: string name; unsigned int age; long salary; public: void setName(string);// assme the implementation void setAge(unsigned int); // assme the implementation void setSalary(long sal); // assme the implementation void setPersonData() { setName("Scott"); setAge(25); selSalary(50000); } }

aquí setPersonData() es una función miembro pero, en última instancia, lo que se hace al hacerlo también puede lograrse al hacer que sea una función no miembro como esta, y mantendrá la interfaz de clase mínima y no engrosará la clase con una gran cantidad de funciones miembro innecesariamente.

void setPersonData(Person &p) { p.setName("Scott"); p.setAge(25); p.selSalary(50000); }


Supongo que el punto general es que es beneficioso implementar siempre las cosas en términos de otras cosas, si puedes. La implementación de la funcionalidad como funciones sin amigos, asegura que esta funcionalidad no se interrumpa si cambia la representación de la clase.

En la vida real, supongo que podría tener un problema: es posible que pueda implementar algo en términos de la interfaz pública con la implementación actual , pero si hay cambios en la clase, esto ya no será posible (y usted '' Necesitaré comenzar a declarar cosas amigos. (Por ejemplo, cuando se trata de optimización algorítmica, la función gratuita podría beneficiarse de algunos datos adicionales almacenados en caché, que no deberían ser expuestos al público).

Por lo tanto, la pauta que obtendría sería usar el sentido común, pero no temer a las funciones libres. No hacen que su código C ++ esté menos orientado a objetos.

Otra cosa es probablemente una interfaz que consiste completamente en getters y setters. Esto apenas encapsula nada.

En el caso de Point en particular, podría obtener la tentación de almacenar los datos como int coords[2] y, a este respecto, los captadores y definidores podrían tener un significado (pero también se podría considerar la facilidad de uso y la facilidad de uso). implementación).

Pero si pasa a clases más complicadas, deberían hacer algo (algunas funciones básicas) que no sea simplemente dar acceso a sus datos.

Cuando se trata de vectores, algunos de sus métodos podrían haber sido funciones gratuitas: asignar (en términos de borrar + insertar), en, atrás, frente (en términos de tamaño + operator[] ), vacío (en términos de tamaño o comenzar) / end), pop_back (borrar + tamaño), push_back (insertar + tamaño), end (comenzar + tamaño), rbegin y rend (comenzar y terminar).

Pero si se toma con rigor, esto podría llevar a interfaces bastante confusas, por ejemplo

for (vector<T>::iterator it = v.begin(); it != end(v); ++it)

Además, aquí habría que considerar las capacidades de otros contenedores. Si std :: list no puede implementar end como una función libre, entonces std :: vector tampoco debería (las plantillas necesitan un patrón uniforme para iterar sobre un contenedor).

Una vez más, use el sentido común.


Pregunta 1

En este caso, el siguiente algoritmo de Meyers le dará funciones de miembro:

  • ¿Necesitan ser virtuales? No.
  • ¿Son operator<< o operator>> ? No.
  • ¿Necesitan conversiones de tipo? No.
  • ¿Se pueden implementar en términos de la interfaz pública? No.
  • Así que hazlos miembros.

Su consejo es solo hacerlos amigos cuando realmente necesitan ser; para favorecer a los no miembros no amigos sobre los miembros sobre los amigos.

Pregunta 2

Las funciones SetXXXX necesitan acceder a la representación interna (privada) de la clase, por lo que no pueden ser no miembros no amigos; entonces, argumenta Meyers, deberían ser miembros en lugar de amigos.

La encapsulación se produce al ocultar los detalles de cómo se implementa la clase; define una interfaz pública por separado de una implementación privada. Si luego inventa una mejor implementación, puede cambiarla sin cambiar la interfaz pública, y cualquier código que utilice la clase seguirá funcionando. Por lo tanto, el "número de funciones que podrían estar interrumpidas" de Meyers cuenta las funciones miembro y amigo (que podemos rastrear fácilmente observando la definición de la clase), pero no las funciones que no son miembros que no son miembros que usan la clase a través de su interfaz pública.

Pregunta 3

Esto ha sido respondido .

Los puntos importantes a quitar del consejo de Meyers son:

  • Diseñar clases para tener interfaces públicas limpias y estables separadas de su implementación privada;
  • Solo haga funciones a los miembros o amigos cuando realmente necesiten acceder a la implementación;
  • Solo hacen funciones de amigos cuando no pueden ser miembros.