Declaración friend
La declaración friend aparece en un cuerpo de clase y concede a una función u otra clase acceso a los miembros privados y protegidos de la clase donde aparece la declaración friend.
Sintaxis
friend declaración-de-función
|
(1) | ||||||||
friend definición-de-función
|
(2) | ||||||||
friend especificador-de-clase-elaborado ;
|
(3) | ||||||||
friend especificador-de-tipo-simple ;
|
(4) | (desde C++11) | |||||||
Descripción
class Y
{
int dato; // miembro privado
// la función no miembro operator<< tendrá acceso a los miembros privados de Y
friend std::ostream& operator<<(std::ostream& out, const Y& o);
friend char* X::foo(int); // funciones miembro de otras clases también pueden
// ser amigas
friend X::X(char), X::~X(); // constructores y destructores pueden ser amigos
};
// la declaración friend no declara una función miembro
// este operador operator<< aun se tiene que definir, como no miembro
std::ostream& operator<<(std::ostream& out, const Y& y)
{
return out << y.dato; // puede acceder al miembro privado Y::dato
}
class X
{
int a;
friend void establecer_amiga(X& p, int i)
{
p.a = i; // esta no es una función miembro
}
public:
void establecer_miembro(int i)
{
a = i; // esta es una función miembro
}
};
friend no necesita declararse previamente.friend se ignora. Esta declaración no genera una declaración adelantada de un tipo nuevo.
class Y {};
class A
{
int dato; // dato miembro privado
class B { }; // tipo anidado privado
enum { a = 100 }; // enumerador privado
friend class X; // declaración adelantada de clase amiga
// (especificador de clase elaborado)
friend Y; // declaración de clase amiga
// (especificador de tipo simple) (desde C++11)
};
class X : A::B // de acuerdo: A::B accesible a un amigo
{
A::B mx; // de acuerdo: A::B accesible a miembro de un amigo
class Y
{
A::B my; // de acuerdo: A::B accesible a miembro anidado de un amigo
};
int v[A::a]; // de acuerdo: A::a accesible a miembro de un amigo
};
Notas
La amistad no es transitiva (un amigo de tu amigo no es tu amigo).
La amistad no se hereda (los hijos de tu amigo no son tus amigos).
No se permiten los especificadores de clase de almacenamiento en las declaraciones de funciones amigas. Una función que se define en una declaración friend tiene enlace externo, mientras que una función que se definió anteriormente mantiene el enlace con el que se definió.
Los especificadores de acceso no tienen efecto en el significado de las declaraciones friend (pueden aparecer en secciones private: o public:, sin ninguna diferencia).
Una declaración de una clase friend no puede definir una nueva clase (friend class X {}; es un error).
Cuando una clase local declara una función no calificada o una clase como amiga, solamente las funciones y clases en el ámbito de no clase más interno son buscadas, no las funciones globales:
class F {};
int f();
int main()
{
extern int g();
class Local // clase Local en la función main()
{
friend int f(); // ERROR: tal función no está declarada en main()
friend int g(); // de acuerdo, existe una declaración de g en main()
friend class F; // amistad con clase local F (definida luego)
friend class ::F; // amistad con la F global
};
class F {}; // F local
}
Un nombre primero declarado en una declaración friend dentro de una clase o una plantilla de clase X se vuelve un miembro del espacio de nombres circundante más interno de X, pero no es visible para la búsqueda (excepto para la búsqueda dependiente de argumento que considera a X) a menos que se proporcione una declaración coincidente en el espacio de nombres. Véase Espacios de nombres para más detalles.
Plantillas amigas
Tanto las declaraciones de plantillas de función como las de plantillas de clase pueden aparecer con el especificador friend en cualquier clase no local o plantilla de clase (aunque solamente las plantillas de función pueden definirse dentro de la clase o plantilla de clase que está concediendo amistad). En este caso, cada especialización de la plantilla se vuelve una amiga, ya sea que se instancie implícitamente, se especialice parcialmente, o se especialice explícitamente.
class A
{
template<typename T>
friend class B; // toda B<T> es una amiga de A
template<typename T>
friend void f(T) {} // toda f<T> es una amiga de A
};
Las declaraciones friend no pueden referirse a especializaciones parciales, pero pueden referirse a especializaciones totales:
template<class T> class A {}; // primaria
template<class T> class A<T*> {}; // parcial
template<> class A<int> {}; // total
class X
{
template<class T> friend class A<T*>; // ERROR
friend class A<int>; // de acuerdo
};
Cuando una declaración friend se refiere a una especialización total de una plantilla de función, la palabra clave inline/constexpr (desde C++11)/consteval (desde C++20) y los argumentos por defecto no pueden usarse.
template<class T> void f(int);
template<> void f<int>(int);
class X
{
friend void f<int>(int x = 1); // ERROR: no se permiten argumentos por defecto
};
Una declaración friend de plantilla puede denominar a un miembro de una plantilla de clase A, que puede ser ya sea una función miembro o un tipo miembro (el tipo debe usar un especificador de tipo elaborado). Tal declaración solamente está bien formada si el último componente de su especificador de nombre anidado (el nombre a la izquierda del último ::) es un id-de-plantilla-simple (el nombre de la plantilla seguido de una lista de argumentos entre paréntesis angulares) que denomina la plantilla de clase. Los parámetros de plantilla de tal declaración friend de plantilla deben ser deducibles a partir del id-de-plantilla-simple.
En este caso, el miembro de cualquier especialización de A o especializaciones parciales de A se vuelve un amigo. Esto no requiere instanciar la plantilla primaria A o una especialización parcial de A: los únicos requisitos son que la deducción de los parámetros de plantilla de A de esa especialización tengan éxito, y que la sustitución de los argumentos de plantilla deducidos en la declaración friend produzca una declaración que sería una redeclaración válida del miembro de la especialización:
// plantilla primaria
template<class T>
struct A
{
struct B { };
void f();
struct D { void g(); };
T h();
template<T U> T i();
};
// especialización total
template<>
struct A<int>
{
struct B { };
int f();
struct D { void g(); };
template<int U> int i();
};
// otra especialización total
template<>
struct A<float*>
{
int *h();
};
// la clase de no-plantilla concede amistad a los miembros de la plantilla de clase A
class X
{
template<class T>
friend struct A<T>::B; // toda A<T>::B es amiga, incluyendo A<int>::B
template<class T>
friend void A<T>::f(); // A<int>::f() no es una amiga porque su signatura
// no coincide, pero p. ej., A<char>::f() es una amiga
// template<class T>
// friend void A<T>::D::g(); // mal formada, la última parte del
// especificador de nombre anidado,
// D en A<T>::D::, no es un id-de-plantilla-simple
template<class T>
friend int* A<T*>::h(); // toda A<T*>::h es amiga: A<float*>::h(), A<int*>::h(), etc
template<class T>
template<T U> // toda instanciación de A<T>::i() y A<int>::i() es amiga,
friend T A<T>::i(); // y por lo tanto, todas las especializaciones de esas
// plantillas defunción
};
|
Los argumentos de plantilla por defecto solamente se permiten en las declaraciones |
(desde C++11) |
Operadores amigos de plantilla
Un caso de uso común para las plantillas amigas es la declaración de una sobrecarga de un operador no miembro que actúa sobre una plantilla de clase, p.ej., operator<<(std::ostream&, const Foo<T>&) para algún tipo definido por el usuario Foo<T>
Tal operador puede definirse en el cuerpo de la clase, que tiene el efecto de generar un operador de no plantilla operator<< separado para cada T y hace a ese operador de no plantilla operator<< un amigo de su Foo<T>.
#include <iostream>
template<typename T>
class Foo
{
public:
Foo(const T& val) : dato(val) {}
private:
T dato;
// genera un operador de no plantilla operator<< para esta T
friend std::ostream& operator<<(std::ostream& os, const Foo& obj)
{
return os << obj.dato;
}
};
int main()
{
Foo<double> obj(1.23);
std::cout << obj << '\n';
}
Salida:
1.23
o la plantilla de función tiene que declararse como una plantilla antes del cuerpo de la clase, en cuyo caso la declaración friend dentro de Foo<T> puede referirse a la especialización total de operator<< para su T:
#include <iostream>
template<typename T>
class Foo; // declaración adelantada para hacer posible la declaración de la función
template<typename T> // declaración
std::ostream& operator<<(std::ostream&, const Foo<T>&);
template<typename T>
class Foo
{
public:
Foo(const T& val) : dato(val) {}
private:
T dato;
// se refiere a la especialización total para esta T en particular
friend std::ostream& operator<< <> (std::ostream&, const Foo&);
// nota: esto depende de la deducción de argumento de plantilla en las declaraciones
// también puede especificar el argumento de plantilla con operator<< <T>
};
// definición
template<typename T>
std::ostream& operator<<(std::ostream& os, const Foo<T>& obj)
{
return os << obj.dato;
}
int main()
{
Foo<double> obj(1.23);
std::cout << obj << '\n';
}
Salida:
1.23
Ejemplo
Los operadores de inserción y extracción frecuentemente se declaran como amigos no miembro:
#include <iostream>
#include <sstream>
class MiClase
{
int i; // los amigos tienen acceso a miembros no públicos,
static inline int id{6}; // no estáticos y estáticos (que pueden ser inline)
friend std::ostream& operator<<(std::ostream& out, const MiClase&);
friend std::istream& operator>>(std::istream& in, MiClase&);
friend void cambiar_id(int);
public:
MiClase(int i = 0) : i(i) {}
};
std::ostream& operator<<(std::ostream& out, const MiClase& mc)
{
return out << "MiClase::id = " << MiClase::id << "; i = " << mc.i;
}
std::istream& operator>>(std::istream& in, MiClase& mc)
{
return in >> mc.i;
}
void cambiar_id(int id)
{
MiClase::id = id;
}
int main()
{
MiClase mc(7);
std::cout << mc << '\n';
// mc.i = 333*2; // error: i es un miembro privado
std::istringstream("100") >> mc;
std::cout << mc << '\n';
// MiClase::id = 222*3; // error: id es un miembro privado
cambiar_id(9);
std::cout << mc << '\n';
}
Salida:
MiClase::id = 6; i = 7
MiClase::id = 6; i = 100
MiClase::id = 9; i = 100
Informes de defectos
Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.
| ID | Aplicado a | Comportamiento según lo publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 45 | C++98 | Miembros de una clase anidados en una clase amiga de T no tienen acceso especial a T
|
Una clase anidada tiene el mismo acceso que la clase que la envuelve |
| CWG 500 | C++98 | Una clase amiga de T no puede heredar de losmiembros privados o protegidos de T, pero su clase anidada sí
|
Ambas pueden heredar de tales miembros |
| CWG 1439 | C++98 | la regla dirigida a las declaraciones de amigas en clases no locales no cubría las declaraciones de plantillas |
cubierta |
| CWG 1477 | C++98 | un nombre declarado por primera vez en una declaración de amiga dentro de una clase o plantilla de clase no estaba visible para la búsqueda si la declaración coincidente se proporciona en otro ámbito de espacio de nombres |
en esta caso es visible para la búsqueda |
| CWG 1804 | C++98 | cuando un miembro de una plantilla de clase es amigo, el miembro correspondiente de especializaciones de especializaciones parciales de la plantilla de clase no era amigo de la clase que otorga amistad |
dichos miembros son también amigos |
| CWG 2379 | C++11 | las declaraciones de amigos que se refiere a especializaciones completas de plantillas de funciones podrían declarase constexpr |
prohibido |
Referencias
- El estándar C++20 (ISO/IEC 14882:2020):
- 11.9.3 Friends [class.friend]
- 13.7.4 Friends [temp.friend]
- El estándar C++17 (ISO/IEC 14882:2017):
- 14.3 Friends [class.friend]
- 17.5.4 Friends [temp.friend]
- El estándar C++14 (ISO/IEC 14882:2014):
- 11.3 Friends [class.friend]
- 14.5.4 Friends [temp.friend]
- El estándar C++11 (ISO/IEC 14882:2011):
- 11.3 Friends [class.friend]
- 14.5.4 Friends [temp.friend]
- El estándar C++98 (ISO/IEC 14882:1998):
- 11.3 Friends [class.friend]
- 14.5.3 Friends [temp.friend]
Véase también
| Declaración de clase | |
| Especificadores de acceso |