las - sobrecarga de operadores c++ pdf
¿Cuándo deberías usar ''amigo'' en C++? (30)
Para hacer TDD muchas veces he usado la palabra clave ''amigo'' en C ++.
¿Puede un amigo saber todo sobre mí?
No, es solo una amistad de una vía: `(
He estado leyendo las Preguntas frecuentes de C ++ y sentía curiosidad por la declaración del friend
. Personalmente nunca lo he usado, sin embargo, estoy interesado en explorar el idioma.
¿Cuál es un buen ejemplo de usar friend
?
Leyendo las preguntas frecuentes un poco más. Me gusta la idea de la sobrecarga del operador <<
>>
y agregar como amigo de esas clases. Sin embargo, no estoy seguro de cómo esto no rompe la encapsulación. ¿Cuándo pueden estas excepciones mantenerse dentro de lo estricto que es OOP?
edición: leer las preguntas frecuentes un poco más. Me gusta la idea de la sobrecarga del operador << >> y agregar como amigo de esas clases, pero no estoy seguro de cómo esto no rompe la encapsulación.
¿Cómo rompería la encapsulación?
Rompe la encapsulación cuando permite el acceso sin restricciones a un miembro de datos. Considera las siguientes clases:
class c1 {
public:
int x;
};
class c2 {
public:
int foo();
private:
int x;
};
class c3 {
friend int foo();
private:
int x;
};
c1
obviamente no está encapsulado. Cualquiera puede leer y modificar x
en ella. No tenemos manera de hacer cumplir ningún tipo de control de acceso.
c2
está obviamente encapsulado. No hay acceso público a x
. Todo lo que puede hacer es llamar a la función foo
, que realiza algunas operaciones significativas en la clase .
c3
? ¿Es eso menos encapsulado? ¿Permite el acceso sin restricciones a x
? ¿Permite el acceso a funciones desconocidas?
No. Permite precisamente una función para acceder a los miembros privados de la clase. Al igual que c2
hizo. Y al igual que c2
, la única función que tiene acceso no es "alguna función aleatoria, desconocida", sino "la función que figura en la definición de clase". Al igual que c2
, podemos ver, solo mirando las definiciones de clase, una lista completa de quién tiene acceso.
Entonces, ¿cómo es exactamente esto menos encapsulado? La misma cantidad de código tiene acceso a los miembros privados de la clase. Y todos los que tienen acceso se enumeran en la definición de clase.
friend
no rompe la encapsulación. Hace que algunos programadores de personas de Java se sientan incómodos, porque cuando dicen "OOP", en realidad quieren decir "Java". Cuando dicen "Encapsulación", no significan que "los miembros privados deben estar protegidos de accesos arbitrarios", sino que "una clase de Java en la que las únicas funciones capaces de acceder a miembros privados son miembros de la clase", aunque esto no tiene ningún sentido. varias razones
Primero, como ya se mostró, es demasiado restrictivo. No hay ninguna razón por la que los métodos de los amigos no puedan hacer lo mismo.
Segundo, no es lo suficientemente restrictivo. Considere una cuarta clase:
class c4 {
public:
int getx();
void setx(int x);
private:
int x;
};
Esto, según la mentalidad de Java mencionada anteriormente, está perfectamente encapsulado. Y, sin embargo, permite que absolutamente cualquier persona lea y modifique x . ¿Cómo es que aún tiene sentido? (pista: no lo hace)
En pocas palabras: la encapsulación consiste en poder controlar qué funciones pueden acceder los miembros privados. No se trata precisamente de dónde se ubican las definiciones de estas funciones.
¿Controla los derechos de acceso de los miembros y las funciones mediante el derecho Privado / Protegido / Público? por lo tanto, asumiendo que la idea de todos y cada uno de esos 3 niveles es clara, entonces debería quedar claro que nos falta algo ...
La declaración de un miembro / función como protegida, por ejemplo, es bastante genérica. Está diciendo que esta función está fuera del alcance de todos (a excepción de un hijo heredado, por supuesto). Pero ¿qué pasa con las excepciones? Cada sistema de seguridad te permite tener algún tipo de ''lista blanca ", ¿verdad?
De modo que amigo le permite tener la flexibilidad de tener aislamiento de objetos sólidos como una roca, pero permite que se cree una "brecha" para las cosas que cree que están justificadas.
Supongo que la gente dice que no es necesario porque siempre hay un diseño que lo hará sin él. Creo que es similar a la discusión de las variables globales: nunca debes usarlas, siempre hay una manera de prescindir de ellas ... pero en realidad, ves casos en los que eso termina siendo la forma (casi) más elegante. .. Creo que este es el mismo caso con amigos.
Realmente no sirve de nada, aparte de permitirle acceder a una variable miembro sin usar una función de configuración
Bueno, esa no es exactamente la forma de verlo. La idea es controlar quién puede acceder a qué, tener o no una función de configuración tiene poco que ver con eso.
Con respecto al operador << y al operador >> no hay una buena razón para hacer que estos operadores sean amigos. Es cierto que no deberían ser funciones miembro, pero tampoco necesitan ser amigos.
Lo mejor que puedes hacer es crear funciones públicas de impresión (ostream &) y de lectura (istream &). Luego, escriba el operador << y el operador >> en términos de esas funciones. Esto le da la ventaja adicional de permitirle hacer esas funciones virtuales, lo que proporciona una serialización virtual.
El creador de C ++ dice que no está rompiendo ningún principio de encapsulación, y lo citaré:
¿"Amigo" viola la encapsulación? No, no lo hace. "Amigo" es un mecanismo explícito para otorgar acceso, al igual que la membresía. No puede (en un programa de conformidad estándar) otorgarse acceso a una clase sin modificar su origen.
Es más que claro ...
El ejemplo canónico es sobrecargar el operador <<. Otro uso común es permitir que un ayudante o administrador acceda a sus partes internas.
Aquí hay un par de pautas que escuché sobre los amigos de C ++. El último es particularmente memorable.
- Tus amigos no son los amigos de tu hijo.
- Los amigos de tu hijo no son tus amigos.
- Solo los amigos pueden tocar sus partes privadas.
El ejemplo de árbol es un ejemplo bastante bueno: tener un objeto implementado en una clase diferente sin tener una relación de herencia.
Tal vez también podría necesitarlo para tener un constructor protegido y forzar a las personas a usar su fábrica de "amigos".
... Ok, francamente puedes vivir sin él.
En C ++, la palabra clave "amigo" es útil en la sobrecarga de operadores y en Making Bridge.
1.) Palabra clave de amigo en sobrecarga de operador:
El ejemplo para la sobrecarga del operador es: Digamos que tenemos una clase "Punto" que tiene dos variables flotantes
"x" (para la coordenada x) e "y" (para la coordenada y). Ahora tenemos que sobrecargar "<<"
(operador de extracción), de modo que si llamamos "cout << pointobj"
, se imprimirán las coordenadas x e y (donde pointobj es un objeto de la clase Point). Para ello tenemos dos opciones:
1.Overload "operator <<()" function in "ostream" class.
2.Overload "operator<<()" function in "Point" class.Ahora la primera opción no es buena porque si necesitamos sobrecargar de nuevo este operador para alguna clase diferente, entonces tenemos que hacer un cambio nuevamente en la clase "ostream".
Es por eso que la segunda es la mejor opción. Ahora el compilador puede llamar a la "operator <<()"
función:
1.Using ostream object cout.As: cout.operator<<(Pointobj) (form ostream class).
2.Call without an object.As: operator<<(cout, Pointobj) (from Point class).
Debido a que hemos implementado la sobrecarga en la clase Point. Entonces, para llamar a esta función sin un objeto, debemos agregar una "friend"
palabra clave porque podemos llamar a una función de amigo sin un objeto. Ahora la declaración de función será Como:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"
2.) Palabra clave de amigo en la creación de bridge:
Supongamos que tenemos que realizar una función en la que tenemos que acceder al miembro privado de dos o más clases (generalmente denominado "bridge"). Cómo hacer esto:
Para acceder al miembro privado de una clase, debe ser miembro de esa clase. Ahora, para acceder al miembro privado de otra clase, cada clase debe declarar que funciona como una función de amigo. Por ejemplo: supongamos que hay dos clases A y B. Una función "funcBridge()"
desea acceder al miembro privado de ambas clases. Entonces ambas clases deben declarar "funcBridge()"
como:
friend return_type funcBridge(A &a_obj, B & b_obj);
Creo que esto ayudaría a entender la palabra clave amigo.
En el trabajo usamos amigos para probar el código , extensivamente. Esto significa que podemos proporcionar la encapsulación adecuada y el ocultamiento de información para el código principal de la aplicación. Pero también podemos tener un código de prueba separado que usa amigos para inspeccionar el estado interno y los datos para la prueba.
Basta con decir que no usaría la palabra clave de amigo como un componente esencial de su diseño.
En primer lugar (IMO) no escuches a las personas que dicen que un friend
no es útil. Es útil. En muchas situaciones, tendrá objetos con datos o funciones que no se pretende que estén disponibles públicamente. Esto es particularmente cierto en el caso de bases de código grandes con muchos autores que pueden estar familiarizados superficialmente con diferentes áreas.
Hay alternativas al especificador de amigo, pero a menudo son engorrosas (clases concretas en cpp a nivel / typedefs enmascarados) o no infalibles (comentarios o convenciones de nombres de funciones).
Sobre la respuesta;
El especificador de friend
permite que la clase designada acceda a los datos protegidos o a la funcionalidad dentro de la clase que realiza la declaración del amigo. Por ejemplo, en el código a continuación, cualquiera puede pedirle a un niño su nombre, pero solo la madre y el niño pueden cambiar el nombre.
Puede llevar este ejemplo simple más lejos considerando una clase más compleja, como una ventana. Es muy probable que una ventana tenga muchos elementos de función / datos que no deberían ser accesibles públicamente, pero SON necesarios para una clase relacionada, como un WindowManager.
class Child
{
//Mother class members can access the private parts of class Child.
friend class Mother;
public:
string name( void );
protected:
void setName( string newName );
};
Encontré un lugar práctico para usar el acceso de amigos: Univest de funciones privadas.
Friend es útil cuando estás construyendo un contenedor y quieres implementar un iterador para esa clase.
La palabra clave friend
tiene una serie de buenos usos. Aquí están los dos usos inmediatamente visibles para mí:
Definición de amigo
La definición de amigo permite definir una función en el ámbito de clase, pero la función no se definirá como una función miembro, sino como una función libre del espacio de nombres que la contiene, y no será visible normalmente, excepto para la búsqueda dependiente del argumento. Eso lo hace especialmente útil para la sobrecarga del operador:
namespace utils {
class f {
private:
typedef int int_type;
int_type value;
public:
// let''s assume it doesn''t only need .value, but some
// internal stuff.
friend f operator+(f const& a, f const& b) {
// name resolution finds names in class-scope.
// int_type is visible here.
return f(a.value + b.value);
}
int getValue() const { return value; }
};
}
int main() {
utils::f a, b;
std::cout << (a + b).getValue(); // valid
}
Clase base privada de CRTP
A veces, encuentra la necesidad de que una política necesite acceso a la clase derivada:
// possible policy used for flexible-class.
template<typename Derived>
struct Policy {
void doSomething() {
// casting this to Derived* requires us to see that we are a
// base-class of Derived.
some_type const& t = static_cast<Derived*>(this)->getSomething();
}
};
// note, derived privately
template<template<typename> class SomePolicy>
struct FlexibleClass : private SomePolicy<FlexibleClass> {
// we derive privately, so the base-class wouldn''t notice that,
// (even though it''s the base itself!), so we need a friend declaration
// to make the base a friend of us.
friend class SomePolicy<FlexibleClass>;
void doStuff() {
// calls doSomething of the policy
this->doSomething();
}
// will return useful information
some_type getSomething();
};
Encontrará un ejemplo no diseñado para eso en this respuesta. Otro código usando eso está en this respuesta. La base CRTP emite este puntero, para poder acceder a los campos de datos de la clase derivada utilizando punteros de miembros de datos.
La respuesta corta sería: usar amigo cuando realmente mejore la encapsulación. Mejorar la legibilidad y la facilidad de uso (los operadores << y >> son el ejemplo canónico) también es una buena razón.
En cuanto a los ejemplos de mejora de la encapsulación, las clases diseñadas específicamente para trabajar con las partes internas de otras clases (las clases de prueba vienen a la mente) son buenos candidatos.
Las funciones y clases de Friend brindan acceso directo a miembros privados y protegidos de la clase para evitar romper la encapsulación en el caso general. La mayoría del uso es con ostream: nos gustaría poder escribir:
Point p;
cout << p;
Sin embargo, esto puede requerir acceso a los datos privados de Point, por lo que definimos el operador sobrecargado
friend ostream& operator<<(ostream& output, const Point& p);
Sin embargo, hay obvias implicaciones de encapsulación. Primero, ahora la clase o función de amigos tiene acceso total a TODOS los miembros de la clase, incluso a aquellos que no pertenecen a sus necesidades. Segundo, las implementaciones de la clase y el amigo ahora están enredados hasta el punto en que un cambio interno en la clase puede romper al amigo.
Si ve al amigo como una extensión de la clase, entonces esto no es un problema, lógicamente hablando. Pero, en ese caso, ¿por qué fue necesario expulsar al amigo en primer lugar?
Para lograr lo mismo que los ''amigos'' pretenden lograr, pero sin romper la encapsulación, uno puede hacer esto:
class A
{
public:
void need_your_data(B & myBuddy)
{
myBuddy.take_this_name(name_);
}
private:
string name_;
};
class B
{
public:
void print_buddy_name(A & myBuddy)
{
myBuddy.need_your_data(*this);
}
void take_this_name(const string & name)
{
cout << name;
}
};
La encapsulación no está rota, la clase B no tiene acceso a la implementación interna en A, pero el resultado es el mismo que si hubiéramos declarado a B un amigo de A. El compilador optimizará las llamadas de función, por lo que esto resultará en lo mismo. Instrucciones como acceso directo.
Creo que usar ''amigo'' es simplemente un atajo con un beneficio discutible, pero con un costo definido.
Otra versión común del ejemplo de Andrew, el temido pareado de código.
parent.addChild(child);
child.setParent(parent);
En lugar de preocuparse si ambas líneas siempre se hacen juntas y en orden consistente, puede hacer que los métodos sean privados y tener una función de amigo para imponer la consistencia:
class Parent;
class Object {
private:
void setParent(Parent&);
friend void addChild(Parent& parent, Object& child);
};
class Parent : public Object {
private:
void addChild(Object& child);
friend void addChild(Parent& parent, Object& child);
};
void addChild(Parent& parent, Object& child) {
if( &parent == &child ){
wetPants();
}
parent.addChild(child);
child.setParent(parent);
}
En otras palabras, puede mantener las interfaces públicas más pequeñas e imponer invariantes que dividen las clases y los objetos en las funciones de amigo.
Otro uso: amigo (+ herencia virtual) se puede usar para evitar derivar de una clase (también conocido como "hacer que una clase no se pueda dividir") => 1 , 2
Desde el 2 :
class Fred;
class FredBase {
private:
friend class Fred;
FredBase() { }
};
class Fred : private virtual FredBase {
public:
...
};
Para hacer TDD muchas veces he usado la palabra clave ''amigo'' en C ++.
¿Puede un amigo saber todo sobre mí?
Actualizado: encontré esta valiosa respuesta sobre la palabra clave "amigo" del sitio Bjarne Stroustrup .
"Amigo" es un mecanismo explícito para otorgar acceso, al igual que la membresía.
Solo estoy usando la palabra clave amiga para las funciones protegidas de unittest. Algunos dirán que no debes probar la funcionalidad protegida. Sin embargo, encuentro esta herramienta muy útil al agregar una nueva funcionalidad.
Sin embargo, no uso la palabra clave directamente en las declaraciones de clase, en lugar de eso uso un ingenioso truco de plantilla para lograr esto:
template<typename T>
class FriendIdentity {
public:
typedef T me;
};
/**
* A class to get access to protected stuff in unittests. Don''t use
* directly, use friendMe() instead.
*/
template<class ToFriend, typename ParentClass>
class Friender: public ParentClass
{
public:
Friender() {}
virtual ~Friender() {}
private:
// MSVC != GCC
#ifdef _MSC_VER
friend ToFriend;
#else
friend class FriendIdentity<ToFriend>::me;
#endif
};
/**
* Gives access to protected variables/functions in unittests.
* Usage: <code>friendMe(this, someprotectedobject).someProtectedMethod();</code>
*/
template<typename Tester, typename ParentClass>
Friender<Tester, ParentClass> &
friendMe(Tester * me, ParentClass & instance)
{
return (Friender<Tester, ParentClass> &)(instance);
}
Esto me permite hacer lo siguiente:
friendMe(this, someClassInstance).someProtectedFunction();
Funciona en GCC y MSVC al menos.
Tienes que tener mucho cuidado cuando / donde usas la palabra clave de friend
y, como tú, la he usado muy raramente. A continuación se presentan algunas notas sobre el uso de friend
y las alternativas.
Digamos que quieres comparar dos objetos para ver si son iguales. Usted podría:
- Use métodos de acceso para hacer la comparación (marque cada ivar y determine la igualdad).
- O bien, puede acceder a todos los miembros directamente haciéndolos públicos.
El problema con la primera opción, es que podría ser MUCHOS accesores, que es (un poco) más lento que el acceso directo a variables, más difícil de leer y engorroso. El problema con el segundo enfoque es que rompes completamente la encapsulación.
Lo que estaría bien, es si pudiéramos definir una función externa que aún pudiera obtener acceso a los miembros privados de una clase. Podemos hacer esto con la palabra clave friend
:
class Beer {
public:
friend bool equal(Beer a, Beer b);
private:
// ...
};
El método equal(Beer, Beer)
ahora tiene acceso directo a los miembros privados de a y b
(que pueden ser char *brand
, float percentAlcohol
, etc.) Este es un ejemplo bastante float percentAlcohol
, antes se aplicaría un friend
a un grupo sobrecargado == operator
, pero vamos a llegar a eso.
Algunas cosas a tener en cuenta:
- Un
friend
NO es una función miembro de la clase - Es una función ordinaria con acceso especial a los miembros privados de la clase.
- No reemplace todos los accesores y mutadores con amigos (¡también puede hacer que todo sea
public
!) - La amistad no es recíproca
- La amistad no es transitiva
- La amistad no se hereda
- O, como lo explica la sección de preguntas frecuentes de C ++ : "Solo porque le concedo la amistad, no le otorgo a sus hijos acceso a mí automáticamente, no le otorga a sus amigos acceso a mí y no me otorga acceso a usted automáticamente. . "
Solo uso a los friends
cuando es mucho más difícil hacerlo de otra manera. Como otro ejemplo, muchas funciones de vector matematicas a menudo se crean como friends
debido a la interoperabilidad de Mat2x2
, Mat3x3
, Mat4x4
, Vec2
, Vec3
, Vec4
, etc. Y es mucho más fácil ser amigos, en lugar de tener que usar accesores en todas partes. Como se señaló, el friend
suele ser útil cuando se aplica a <<
(realmente útil para la depuración), >>
y quizás al operador ==
, pero también se puede usar para algo como esto:
class Birds {
public:
friend Birds operator +(Birds, Birds);
private:
int numberInFlock;
};
Birds operator +(Birds b1, Birds b2) {
Birds temp;
temp.numberInFlock = b1.numberInFlock + b2.numberInFlock;
return temp;
}
Como digo, no uso a friend
muy a menudo, pero de vez en cuando es justo lo que necesitas. ¡Espero que esto ayude!
Tuvimos un problema interesante que surgió en una empresa en la que trabajé anteriormente, en la que usamos un amigo para un efecto decente. Trabajé en nuestro departamento marco, creamos un sistema básico de motor a través de nuestro sistema operativo personalizado. Internamente teníamos una estructura de clase:
Game
/ /
TwoPlayer SinglePlayer
Todas estas clases fueron parte del marco y mantenidas por nuestro equipo. Los juegos producidos por la compañía se construyeron sobre este marco derivado de uno de los niños de los Juegos. El problema era que el juego tenía interfaces para varias cosas a las que SinglePlayer y TwoPlayer necesitaban acceso, pero que no queríamos exponer fuera de las clases de framework. La solución fue hacer que esas interfaces fueran privadas y permitirles el acceso a TwoPlayer y SinglePlayer a través de la amistad.
Sinceramente, todo este problema se podría haber resuelto con una mejor implementación de nuestro sistema, pero estábamos atados a lo que teníamos.
Una instancia específica en la que uso un friend
es al crear clases de Singleton . La palabra clave friend
me permite crear una función de acceso, que es más concisa que tener siempre un método "GetInstance ()" en la clase.
/////////////////////////
// Header file
class MySingleton
{
private:
// Private c-tor for Singleton pattern
MySingleton() {}
friend MySingleton& GetMySingleton();
}
// Accessor function - less verbose than having a "GetInstance()"
// static function on the class
MySingleton& GetMySingleton();
/////////////////////////
// Implementation file
MySingleton& GetMySingleton()
{
static MySingleton theInstance;
return theInstance;
}
@roo : La encapsulación no se rompe aquí porque la clase misma dicta quién puede acceder a sus miembros privados. La encapsulación solo se rompería si esto pudiera ser causado fuera de la clase, por ejemplo, si su operator <<
proclamara "Soy un amigo de la clase foo
".
friend
reemplaza uso de public
, no uso de private
!
En realidad, las preguntas frecuentes de C ++ ya responden esto .
Al implementar algoritmos de árbol para la clase, el código de marco que el profesor nos dio tenía la clase de árbol como amigo de la clase de nodo.
Realmente no sirve de nada, aparte de permitirle acceder a una variable miembro sin usar una función de configuración.
Esto puede no ser una situación de caso de uso real, pero puede ayudar a ilustrar el uso de un amigo entre clases.
La casa club
class ClubHouse {
public:
friend class VIPMember; // VIP Members Have Full Access To Class
private:
unsigned nonMembers_;
unsigned paidMembers_;
unsigned vipMembers;
std::vector<Member> members_;
public:
ClubHouse() : nonMembers_(0), paidMembers_(0), vipMembers(0) {}
addMember( const Member& member ) { // ...code }
void updateMembership( unsigned memberID, Member::MembershipType type ) { // ...code }
Amenity getAmenity( unsigned memberID ) { // ...code }
protected:
void joinVIPEvent( unsigned memberID ) { // ...code }
}; // ClubHouse
Los miembros de la clase
class Member {
public:
enum MemberShipType {
NON_MEMBER_PAID_EVENT, // Single Event Paid (At Door)
PAID_MEMBERSHIP, // Monthly - Yearly Subscription
VIP_MEMBERSHIP, // Highest Possible Membership
}; // MemberShipType
protected:
MemberShipType type_;
unsigned id_;
Amenity amenity_;
public:
Member( unsigned id, MemberShipType type ) : id_(id), type_(type) {}
virtual ~Member(){}
unsigned getId() const { return id_; }
MemberShipType getType() const { return type_; }
virtual void getAmenityFromClubHouse() = 0
};
class NonMember : public Member {
public:
explicit NonMember( unsigned id ) : Member( id, MemberShipType::NON_MEMBER_PAID_EVENT ) {}
void getAmenityFromClubHouse() override {
Amenity = ClubHouse::getAmenity( this->id_ );
}
};
class PaidMember : public Member {
public:
explicit PaidMember( unsigned id ) : Member( id, MemberShipType::PAID_MEMBERSHIP ) {}
void getAmenityFromClubHouse() override {
Amenity = ClubHouse::getAmenity( this->id_ );
}
};
class VIPMember : public Member {
public:
friend class ClubHouse;
public:
explicit VIPMember( unsigned id ) : Member( id, MemberShipType::VIP_MEMBERSHIP ) {}
void getAmenityFromClubHouse() override {
Amenity = ClubHouse::getAmenity( this->id_ );
}
void attendVIPEvent() {
ClubHouse::joinVIPEvent( this->id );
}
};
Comodidades
class Amenity{};
Si miras la relación de estas clases aquí; El ClubHouse tiene una variedad de diferentes tipos de membresías y acceso de membresía. Todos los miembros se derivan de una clase super o base, ya que todos comparten un ID y un tipo enumerado que son comunes y las clases externas pueden acceder a sus ID y tipos a través de funciones de acceso que se encuentran en la clase base.
Sin embargo, a través de este tipo de jerarquía de los Miembros y sus clases Derivadas y su relación con la clase ClubHouse, la única clase derivada que tiene "privilegios especiales" es la clase VIPMember. La clase base y las otras 2 clases derivadas no pueden acceder al método joinVIPEvent () de ClubHouse, sin embargo, la clase Miembro VIP tiene ese privilegio como si tuviera acceso completo a ese evento.
Así que con VIPMember y ClubHouse es una vía de acceso bidireccional donde las otras Clases para miembros son limitadas.
Puede usar la amistad cuando diferentes clases (no heredar una de la otra) están usando miembros privados o protegidos de la otra clase.
Los casos de uso típicos de las funciones de amistad son operaciones que se realizan entre dos clases diferentes que acceden a miembros privados o protegidos de ambas.
de http://www.cplusplus.com/doc/tutorial/inheritance/ .
Puede ver este ejemplo donde el método que no es miembro accede a los miembros privados de una clase. Este método debe ser declarado en esta misma clase como amigo de la clase.
// friend functions
#include <iostream>
using namespace std;
class Rectangle {
int width, height;
public:
Rectangle() {}
Rectangle (int x, int y) : width(x), height(y) {}
int area() {return width * height;}
friend Rectangle duplicate (const Rectangle&);
};
Rectangle duplicate (const Rectangle& param)
{
Rectangle res;
res.width = param.width*2;
res.height = param.height*2;
return res;
}
int main () {
Rectangle foo;
Rectangle bar (2,3);
foo = duplicate (bar);
cout << foo.area() << ''/n'';
return 0;
}
Como dice la referencia para la declaración del amigo :
La declaración de amigo aparece en el cuerpo de una clase y otorga una función u otra clase de acceso a los miembros privados y protegidos de la clase donde aparece la declaración de amigo.
Entonces, solo como recordatorio, hay errores técnicos en algunas de las respuestas que dicen que friend
solo pueden visitar miembros protegidos .
Los amigos también son útiles para las devoluciones de llamada. Podrías implementar callbacks como métodos estáticos.
class MyFoo
{
private:
static void callback(void * data, void * clientData);
void localCallback();
...
};
donde callback
llama localCallback
internamente, y clientData
tiene su instancia en ella. En mi opinión,
o...
class MyFoo
{
friend void callback(void * data, void * callData);
void localCallback();
}
Lo que esto permite es que el amigo esté definido puramente en el cpp como una función de estilo c, y no abarrote la clase.
De manera similar, un patrón que he visto muy a menudo es colocar a todos los miembros realmente privados de una clase en otra clase, que se declara en el encabezado, se define en el cpp y se hace amigo. Esto permite al codificador ocultar gran parte de la complejidad y el trabajo interno de la clase al usuario del encabezado.
En el encabezado:
class MyFooPrivate;
class MyFoo
{
friend class MyFooPrivate;
public:
MyFoo();
// Public stuff
private:
MyFooPrivate _private;
// Other private members as needed
};
En el cpp,
class MyFooPrivate
{
public:
MyFoo *owner;
// Your complexity here
};
MyFoo::MyFoo()
{
this->_private->owner = this;
}
Se vuelve más fácil ocultar las cosas que las corrientes no necesitan ver de esta manera.
Probablemente me haya pasado algo por alto en las respuestas anteriores, pero otro concepto importante en la encapsulación es ocultar la implementación. La reducción del acceso a los miembros de datos privados (los detalles de implementación de una clase) permite una modificación mucho más fácil del código más adelante. Si un amigo accede directamente a los datos privados, cualquier cambio en los campos de datos de implementación (datos privados), rompe el código que accede a esos datos. El uso de métodos de acceso elimina principalmente esto. Bastante importante, pensaría yo.
Usted podría adherirse a los principios más estricta y pura programación orientada a objetos y asegurarse de que no hay miembros de datos de cualquier clase, incluso tienen descriptores de acceso de modo que todos los objetos deben ser los únicos que se conocen acerca de sus datos con la única manera de actuar sobre ellos es a través indirectos mensajes , Es decir, los métodos.
Pero incluso C # tiene una palabra clave de visibilidad interna y Java tiene su accesibilidad predeterminada a nivel de paquete para algunas cosas. C ++ se acerca realmente al ideal OOP al minimizar el compromiso de visibilidad en una clase al especificar exactamente qué otra clase y solo otras clases podrían ver en ella.
Realmente no uso C ++, pero si C # tuviera amigos , lo haría en lugar del modificador interno global del ensamblado , que en realidad uso mucho. Realmente no rompe la incapsulación, porque la unidad de implementación en .NET es un ensamblaje.
Pero luego está el Atributo InternalsVisibleTo (otro Ensamblaje) que actúa como un mecanismo de amistad entre ensamblajes . Microsoft usa esto para ensamblajes de diseñadores visuales .