Namespaces
Variants

Friend declaration

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Access specifiers
friend specifier

Class-specific function properties
Special member functions
Templates
Miscellaneous

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 ;

friend especificador-de-tipo ;

(4) (desde C++11)
(hasta C++26)
friend lista-de-especificadores-de-tipo-amigo ; (5) (desde C++26)
1,2) Una declaración de función amiga.
3-5) Una declaración de amigo de clase.
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

1) Designa una función o varias funciones como amigas de esta clase:
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
}
2) (solo permitido en definiciones de clases no locales ) Define una función no miembro y la convierte en amiga de esta clase al mismo tiempo. Dicha función no miembro es siempre inline , a menos que esté asociada a un módulo nombrado (desde C++20) .
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
    }
};
3,4) Designa una clase como amiga de esta clase. Esto significa que las declaraciones y definiciones de miembros de la clase amiga pueden acceder a miembros privados y protegidos de esta clase, y también que la clase amiga puede heredar de miembros privados y protegidos de esta clase.
3) La clase se denomina mediante elaborated-type-specifier . El nombre de la clase que se utiliza en esta declaración friend no necesita estar declarado previamente.
4) La clase se denomina mediante simple-type-specifier o typename-specifier . Si el tipo nombrado no es un tipo de clase, esta declaración de amigo se ignora. Esta declaración no adelantará la declaración de un nuevo tipo.
5) Designa todas las clases en friend-type-specifier-list como amigas de esta clase. Esto significa que las declaraciones y definiciones de miembros de los amigos pueden acceder a miembros privados y protegidos de esta clase y también que los amigos pueden heredar de miembros privados y protegidos de esta clase. Si un tipo nombrado no es un tipo de clase, se ignora en esta declaración de amistad.
Cada especificador en friend-type-specifier-list nombra una clase si el especificador no va seguido de puntos suspensivos; de lo contrario, se aplica pack expansion .
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

friend

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