Friend declaration
La declaración friend aparece en un class body y otorga a una función u otra clase acceso a los miembros privados y protegidos de la clase donde aparece la declaración friend.
Contenidos |
Sintaxis
friend
declaración-de-función
|
(1) | ||||||||
friend
definición-de-función
|
(2) | ||||||||
friend
especificador-de-tipo-elaborado
;
|
(3) | (hasta C++26) | |||||||
friend
especificador-de-tipo-simple
;
|
(4) |
(desde C++11)
(hasta C++26) |
|||||||
friend
lista-de-especificadores-de-tipo-amigo
;
|
(5) | (desde C++26) | |||||||
| function-declaration | - | una declaración de función |
| function-definition | - | una definición de función |
| elaborated-type-specifier | - | un especificador de tipo elaborado |
| simple-type-specifier | - | un especificador de tipo simple |
| typename-specifier | - | la palabra clave typename seguida de un identificador calificado o un identificador de plantilla simple calificado |
| friend-type-specifier-list | - |
una lista no vacía separada por comas de
simple-type-specifier
,
elaborated-type-specifier
, y
typename-specifier
s, cada especificador puede ir seguido de puntos suspensivos (
...
)
|
Descripción
class Y { int data; // private member // the non-member function operator<< will have access to Y's private members friend std::ostream& operator<<(std::ostream& out, const Y& o); friend char* X::foo(int); // members of other classes can be friends too friend X::X(char), X::~X(); // constructors and destructors can be friends }; // friend declaration does not declare a member function // this operator<< still needs to be defined, as a non-member std::ostream& operator<<(std::ostream& out, const Y& y) { return out << y.data; // can access private member Y::data }
class X { int a; friend void friend_set(X& p, int i) { p.a = i; // this is a non-member function } public: void member_set(int i) { a = i; // this is a member function } };
class Y {}; class A { int data; // private data member class B {}; // private nested type enum { a = 100 }; // private enumerator friend class X; // friend class forward declaration (elaborated class specifier) friend Y; // friend class declaration (simple type specifier) (since C++11) // the two friend declarations above can be merged since C++26: // friend class X, Y; }; class X : A::B // OK: A::B accessible to friend { A::B mx; // OK: A::B accessible to member of friend class Y { A::B my; // OK: A::B accessible to nested member of friend }; int v[A::a]; // OK: A::a accessible to member of friend };
Amigos de plantilla
Tanto las declaraciones de
function template
como de
class template
pueden aparecer con el especificador
friend
en cualquier clase o class template no local (aunque solo las function templates pueden definirse dentro de la clase o class template que otorga la amistad). En este caso, cada especialización de la plantilla se convierte en amiga, ya sea que se instancie implícitamente, se especialice parcialmente o se especialice explícitamente.
class A { template<typename T> friend class B; // cada B<T> es amigo de A template<typename T> friend void f(T) {} // cada f<T> es amigo de A };
Las declaraciones de amigo no pueden hacer referencia a especializaciones parciales, pero pueden hacer referencia a especializaciones completas:
template<class T> class A {}; // primaria template<class T> class A<T*> {}; // parcial template<> class A<int> {}; // completa class X { template<class T> friend class A<T*>; // Error friend class A<int>; // Correcto };
Cuando una declaración friend se refiere a una especialización completa de una plantilla de función, las palabras clave inline , constexpr (desde C++11) , consteval (desde C++20) y los argumentos predeterminados no pueden utilizarse:
template<class T> void f(int); template<> void f<int>(int); class X { friend void f<int>(int x = 1); // error: argumentos predeterminados no permitidos };
Una declaración de amigo de plantilla puede nombrar a un miembro de una plantilla de clase A, que puede ser una función miembro o un tipo miembro (el tipo debe usar
elaborated-type-specifier
). Tal declaración solo está bien formada si el último componente en su especificador de nombre anidado (el nombre a la izquierda del último
::
) es un simple-template-id (nombre de plantilla seguido de lista de argumentos entre corchetes angulares) que nombra la plantilla de clase. Los parámetros de plantilla de dicha declaración de amigo de plantilla deben ser deducibles del simple-template-id.
En este caso, el miembro de cualquier especialización de A o de especializaciones parciales de A se convierte en un amigo. Esto no implica instanciar la plantilla primaria A ni las especializaciones parciales de A: los únicos requisitos son que la deducción de los parámetros de plantilla de A a partir de esa especialización tenga éxito, y que la sustitución de los argumentos de plantilla deducidos en la declaración de amistad 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 completa template<> struct A<int> { struct B {}; int f(); struct D { void g(); }; template<int U> int i(); }; // otra especialización completa template<> struct A<float*> { int *h(); }; // la clase no plantilla que otorga amistad a miembros de la plantilla de clase A class X { template<class T> friend struct A<T>::B; // todos los A<T>::B son amigos, incluyendo A<int>::B template<class T> friend void A<T>::f(); // A<int>::f() no es amigo porque su firma // no coincide, pero p.ej. A<char>::f() es amigo // template<class T> // friend void A<T>::D::g(); // mal formado, la última parte del especificador de nombre anidado, // // D en A<T>::D::, no es un simple-template-id template<class T> friend int* A<T*>::h(); // todos los A<T*>::h son amigos: // A<float*>::h(), A<int*>::h(), etc template<class T> template<T U> // todas las instanciaciones de A<T>::i() y A<int>::i() son amigos, friend T A<T>::i(); // y por lo tanto todas las especializaciones de esas plantillas de función };
|
Argumentos de plantilla predeterminados solo están permitidos en declaraciones de función amiga de plantilla si la declaración es una definición y no aparecen otras declaraciones de esta plantilla de función en esta unidad de traducción. |
(desde C++11) |
Operadores friend de plantilla
Un caso de uso común para las funciones amigas de plantilla es la declaración de un operador no miembro sobrecargado que actúa sobre una plantilla de clase, por ejemplo operator << ( std:: ostream & , const Foo < T > & ) para alguna Foo < T > definida por el usuario.
Dicho operador puede definirse en el cuerpo de la clase, lo cual tiene el efecto de generar un
operator
<<
no plantilla separado para cada
T
y hace que ese
operator
<<
no plantilla sea amigo de su
Foo
<
T
>
:
#include <iostream> template<typename T> class Foo { public: Foo(const T& val) : data(val) {} private: T data; // generates a non-template operator<< for this T friend std::ostream& operator<<(std::ostream& os, const Foo& obj) { return os << obj.data; } }; int main() { Foo<double> obj(1.23); std::cout << obj << '\n'; }
Salida:
1.23
o la plantilla de función debe declararse como plantilla antes del cuerpo de la clase, en cuyo caso la declaración friend dentro de
Foo
<
T
>
puede referirse a la especialización completa de
operator
<<
para su
T
:
#include <iostream> template<typename T> class Foo; // declaración anticipada para hacer posible la declaración de 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) : data(val) {} private: T data; // se refiere a una especialización completa para este T particular friend std::ostream& operator<< <> (std::ostream&, const Foo&); // nota: esto se basa en la deducción de argumentos de plantilla en declaraciones // también se 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.data; } int main() { Foo<double> obj(1.23); std::cout << obj << '\n'; }
Vinculación
Especificadores de clase de almacenamiento no están permitidos en declaraciones friend.
|
Si una función o plantilla de función se declara y define primero en una declaración friend, y la clase contenedora se define dentro de una declaración de exportación , su nombre tiene el mismo enlace que el nombre de la clase contenedora. |
(since C++20) |
Si (hasta C++20) De lo contrario, si (desde C++20) una función o plantilla de función se declara en una declaración friend, y una declaración no friend correspondiente es accesible, el nombre tiene la vinculación determinada por esa declaración previa.
De lo contrario, la vinculación del nombre introducido por una declaración friend se determina como de costumbre.
Notas
La amistad no es transitiva (un amigo de tu amigo no es tu amigo).
La amistad no se hereda (los hijos de tus amigos no son tus amigos, y tus amigos no son amigos de tus hijos).
Especificadores de acceso no tienen efecto sobre el significado de las declaraciones friend (pueden aparecer en private : o en public : sections, sin diferencia alguna).
Una declaración de clase friend no puede definir una nueva clase ( friend class X { } ; es un error).
Cuando una clase local declara una función o clase sin calificar como amiga, solo se buscan las funciones y clases en el ámbito no-clase más interno, 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, no existe tal función declarada en main() friend int g(); // OK, existe una declaración para g en main() friend class F; // establece amistad con una F local (definida posteriormente) friend class ::F; // establece amistad con la F global }; class F {}; // F local }
Un nombre declarado por primera vez en una declaración friend dentro de una clase o plantilla de clase
X
se convierte en miembro del espacio de nombres envolvente más interno de
X
, pero no es visible para la búsqueda (excepto la búsqueda dependiente de argumentos que considera
X
) a menos que se proporcione una declaración coincidente en el ámbito del espacio de nombres - consulte
namespaces
para más detalles.
| Macro de prueba de características | Valor | Std | Característica |
|---|---|---|---|
__cpp_variadic_friend
|
202403L
|
(C++26) | Declaraciones friend variádicas |
Palabras clave
Ejemplo
Los operadores de inserción y extracción de flujo a menudo se declaran como amigos no miembros:
#include <iostream> #include <sstream> class MyClass { int i; // friends have access to non-public, non-static static inline int id{6}; // and static (possibly inline) members friend std::ostream& operator<<(std::ostream& out, const MyClass&); friend std::istream& operator>>(std::istream& in, MyClass&); friend void change_id(int); public: MyClass(int i = 0) : i(i) {} }; std::ostream& operator<<(std::ostream& out, const MyClass& mc) { return out << "MyClass::id = " << MyClass::id << "; i = " << mc.i; } std::istream& operator>>(std::istream& in, MyClass& mc) { return in >> mc.i; } void change_id(int id) { MyClass::id = id; } int main() { MyClass mc(7); std::cout << mc << '\n'; // mc.i = 333*2; // error: i is a private member std::istringstream("100") >> mc; std::cout << mc << '\n'; // MyClass::id = 222*3; // error: id is a private member change_id(9); std::cout << mc << '\n'; }
Salida:
MyClass::id = 6; i = 7 MyClass::id = 6; i = 100 MyClass::id = 9; i = 100
Informes de defectos
Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.
| DR | Aplicado a | Comportamiento publicado | Comportamiento correcto |
|---|---|---|---|
| CWG 45 | C++98 |
los miembros de una clase anidada en una clase
amiga de
T
no tenían acceso especial a
T
|
una clase anidada tiene el mismo
acceso que la clase contenedora |
| CWG 500 | C++98 |
la clase amiga de
T
no podía heredar de miembros privados o
protegidos de
T
, pero su clase anidada sí podía
|
ambas pueden heredar
de dichos miembros |
| CWG 1439 | C++98 |
la regla dirigida a declaraciones friend en clases
no locales no cubría declaraciones de plantillas |
cubierto |
| CWG 1477 | C++98 |
un nombre declarado por primera vez en una declaración friend dentro de una clase
o plantilla de clase no era visible para búsqueda si la declaración coincidente se proporcionaba en otro ámbito de espacio de nombres |
es visible para
búsqueda en este caso |
| CWG 1804 | C++98 |
cuando se declara como friend un miembro de una plantilla de clase, el miembro
correspondiente de especializaciones de especializaciones parciales de la plantilla de clase no era friend de la clase que otorgaba la amistad |
dichos miembros
también son friends |
| CWG 2379 | C++11 |
las declaraciones friend que se refieren a especializaciones completas
de plantillas de función podían declararse constexpr |
prohibido |
| CWG 2588 | C++98 | los linkages de nombres introducidos por declaraciones friend no estaban claros | aclarado |
Referencias
- Estándar C++23 (ISO/IEC 14882:2024):
-
- 11.8.4 Amigos [class.friend]
-
- 13.7.5 Amigos [temp.friend]
- Estándar C++20 (ISO/IEC 14882:2020):
-
- 11.9.3 Amigos [class.friend]
-
- 13.7.4 Amigos [temp.friend]
- Estándar C++17 (ISO/IEC 14882:2017):
-
- 14.3 Amigos [class.friend]
-
- 17.5.4 Amigos [temp.friend]
- Estándar C++14 (ISO/IEC 14882:2014):
-
- 11.3 Amigos [class.friend]
-
- 14.5.4 Amigos [temp.friend]
- Estándar C++11 (ISO/IEC 14882:2011):
-
- 11.3 Amigos [class.friend]
-
- 14.5.4 Amigos [temp.friend]
- Estándar C++98 (ISO/IEC 14882:1998):
-
- 11.3 Amigos [class.friend]
-
- 14.5.3 Amigos [temp.friend]
Véase también
| Class types | define tipos que contienen varios miembros de datos |
| Access specifiers | define la visibilidad de los miembros de clase |