Spec-Zone.ru › C++

Объявление друга

Объявление друга появляется в теле класса и предоставляет функции или другому классу доступ к закрытым и защищённым членам класса, в котором появляется объявление друга.

Синтаксис

friend function-declaration (1)
friend function-definition (2)
friend elaborated-type-specifier ; (3) (до C++26)
friend simple-type-specifier ;

friend typename-specifier ;

(4) (начиная с C++11)
(до C++26)
friend friend-type-specifier-list ; (5) (начиная с C++26)
1,2) Объявление функции-друга.
3-5) Объявление класса-друга.
function-declaration - объявление функции
function-definition - определение функции
elaborated-type-specifier - уточнённый спецификатор типа
simple-type-specifier - простой спецификатор типа
typename-specifier - ключевое слово typename, за которым следует квалифицированный идентификатор или квалифицированный простой идентификатор шаблона
friend-type-specifier-list - непустой список, разделённый запятыми, из simple-type-specifier, elaborated-type-specifier и typename-specifier ов; за каждым спецификатором может следовать многоточие (...)

Описание

1) Объявляет функцию или несколько функций друзьями этого класса:
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) (разрешено только в определениях классов, не являющихся локальными) Определяет нешаблонную функцию и одновременно делает её другом этого класса. Такая нешаблонная функция всегда является встраиваемой, если только она не принадлежит к именованному модулю(начиная с 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) Объявляет класс другом этого класса. Это означает, что объявления и определения членов класса-друга могут обращаться к закрытым и защищённым членам этого класса, а также что класс-друг может наследовать закрытые и защищённые члены этого класса.
3) Класс задаётся с помощью elaborated-type-specifier. Имя класса, используемое в этом объявлении друга, не обязательно должно быть объявлено ранее.
4) Класс задаётся с помощью simple-type-specifier или typename-specifier. Если указанный тип не является типом класса, это объявление друга игнорируется. Это объявление не выполняет предварительное объявление нового типа.
5) Объявляет все классы из friend-type-specifier-list друзьями этого класса. Это означает, что объявления и определения членов классов-друзей могут обращаться к закрытым и защищённым членам этого класса, а также что классы-друзья могут наследовать закрытые и защищённые члены этого класса. Если указанный тип не является типом класса, он игнорируется в этом объявлении друга.
Каждый спецификатор в friend-type-specifier-list задаёт класс, если за спецификатором не следует многоточие; в противном случае применяется раскрытие пакета.
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
};

Шаблонные друзья

Объявления как шаблонов функций, так и шаблонов классов могут использовать спецификатор friend в любом нелокальном классе или шаблоне класса (хотя внутри класса или шаблона класса, предоставляющего дружбу, можно определять только шаблоны функций). В этом случае каждый экземпляр шаблона становится другом независимо от того, был ли он неявно инстанцирован, частично специализирован или явно специализирован.

class A
{
    template<typename T>
    friend class B; // every B<T> is a friend of A
    
    template<typename T>
    friend void f(T) {} // every f<T> is a friend of A
};

Объявления друзей не могут ссылаться на частичные специализации, но могут ссылаться на полные специализации:

template<class T>
class A {};      // primary

template<class T>
class A<T*> {};  // partial

template<>
class A<int> {}; // full

class X
{
    template<class T>
    friend class A<T*>;  // Error
    
    friend class A<int>; // OK
};

Когда объявление друга ссылается на полную специализацию шаблона функции, нельзя использовать ключевое слово inline, constexpr(начиная с C++11), consteval(начиная с C++20) и аргументы по умолчанию:

template<class T>
void f(int);

template<>
void f<int>(int);

class X
{
    friend void f<int>(int x = 1); // error: default args not allowed
};

Объявление шаблонного друга может указывать на член шаблона класса A, которым может быть функция-член или тип-член (для типа необходимо использовать elaborated-type-specifier). Такое объявление корректно сформировано только в том случае, если последний компонент его вложенного спецификатора имени (имя слева от последнего ::) является simple-template-id (именем шаблона, за которым в угловых скобках следует список аргументов), задающим шаблон класса. Параметры шаблона такого объявления шаблонного друга должны выводиться из simple-template-id.

В этом случае член любого экземпляра A или частичной специализации A становится другом. При этом первичный шаблон A или частичные специализации A не инстанцируются: требуется лишь, чтобы вывод параметров шаблона A из этой специализации завершился успешно, а подстановка выведенных аргументов шаблона в объявление друга дала объявление, которое было бы корректным повторным объявлением члена специализации:

// primary template
template<class T>
struct A
{ 
    struct B {};
    
    void f();
    
    struct D { void g(); };
    
    T h();
    
    template<T U>
    T i();
};

// full specialization
template<>
struct A<int>
{
    struct B {};
    
    int f();
    
    struct D { void g(); };
    
    template<int U>
    int i();
};

// another full specialization
template<>
struct A<float*>
{
    int *h();
};

// the non-template class granting friendship to members of class template A
class X
{
    template<class T>
    friend struct A<T>::B; // all A<T>::B are friends, including A<int>::B
    
    template<class T>
    friend void A<T>::f(); // A<int>::f() is not a friend because its signature
                           // does not match, but e.g. A<char>::f() is a friend
    
//  template<class T>
//  friend void A<T>::D::g(); // ill-formed, the last part of the nested-name-specifier,
//                            // D in A<T>::D::, is not simple-template-id
    
    template<class T>
    friend int* A<T*>::h(); // all A<T*>::h are friends:
                            // A<float*>::h(), A<int*>::h(), etc
    
    template<class T> 
    template<T U>       // all instantiations of A<T>::i() and A<int>::i() are friends, 
    friend T A<T>::i(); // and thereby all specializations of those function templates
};

Аргументы шаблона по умолчанию разрешены в объявлениях шаблонных друзей только в том случае, если объявление является определением и в этой единице трансляции нет других объявлений этого шаблона функции.

(начиная с C++11)

Шаблонные операторы-друзья

Распространённый пример использования шаблонных друзей — объявление нешаблонной перегрузки оператора, применяемой к шаблону класса, например operator<<(std::ostream&, const Foo<T>&) для пользовательского типа Foo<T>.

Такой оператор можно определить в теле класса; это приведёт к созданию отдельного нешаблонного operator<< для каждого T и сделает этот нешаблонный operator<< другом соответствующего 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';
}

Вывод:

1.23

или же шаблон функции необходимо объявить как шаблон до тела класса; в этом случае объявление друга внутри Foo<T> может ссылаться на полную специализацию operator<< для своего T:

#include <iostream>

template<typename T>
class Foo; // forward declare to make function declaration possible

template<typename T> // declaration
std::ostream& operator<<(std::ostream&, const Foo<T>&);

template<typename T>
class Foo
{
public:
    Foo(const T& val) : data(val) {}
private:
    T data;
    
    // refers to a full specialization for this particular T 
    friend std::ostream& operator<< <> (std::ostream&, const Foo&);
    
    // note: this relies on template argument deduction in declarations
    // can also specify the template argument with operator<< <T>"
};

// definition
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';
}

Связывание

Спецификаторы класса хранения не допускаются в объявлениях друзей.

Если функция или шаблон функции впервые объявляется и определяется в объявлении друга, а охватывающий класс определён внутри экспортирующих объявлений, его имя имеет такое же связывание, как имя охватывающего класса.

(начиная с C++20)

Если(до C++20)В противном случае, если(начиная с C++20) функция или шаблон функции объявлены в объявлении друга и доступно соответствующее объявление не-друга, имя имеет связывание, определяемое этим предыдущим объявлением.

В противном случае связывание имени, введённого объявлением друга, определяется обычным образом.

Примечания

Дружба не является транзитивной (друг вашего друга не становится вашим другом).

Дружба не наследуется (дети вашего друга не становятся вашими друзьями, а ваши друзья не становятся друзьями ваших детей).

Спецификаторы доступа не влияют на смысл объявлений друзей (они могут находиться в секциях private: или public:, и разницы не будет).

Объявление класса-друга не может определять новый класс (friend class X {}; является ошибкой).

Когда локальный класс объявляет неквалифицированную функцию или класс другом, поиск выполняется только среди функций и классов во вложенной области видимости, ближайшей к внешней, которая не является областью видимости класса, а не среди глобальных функций:

class F {};

int f();

int main()
{
    extern int g();
    
    class Local // Local class in the main() function
    {
        friend int f(); // Error, no such function declared in main()
        friend int g(); // OK, there is a declaration for g in main()
        friend class F; // friends a local F (defined later)
        friend class ::F; // friends the global F
    };
    
    class F {}; // local F
}

Имя, впервые объявленное в объявлении друга внутри класса или шаблона класса, X становится членом пространства имён, непосредственно охватывающего X, но не видимо при поиске (за исключением поиска с учётом аргументов, который рассматривает X), если только не предоставлено соответствующее объявление на уровне пространства имён — подробности см. в разделе пространства имён.

Макрос проверки поддержки возможности Значение Стандарт Возможность
__cpp_variadic_friend 202403L (C++26) Вариативные объявления друзей

Ключевые слова

friend

Пример

Операторы вставки и извлечения из потока часто объявляются как нешаблонные друзья:

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

Вывод:

MyClass::id = 6; i = 7
MyClass::id = 6; i = 100
MyClass::id = 9; i = 100

Отчёты об ошибках

Следующие отчёты об ошибках, изменяющие поведение, были ретроактивно применены к ранее опубликованным стандартам C++.

DR Применён к Поведение согласно опубликованному стандарту Корректное поведение
CWG 45 C++98 члены класса, вложенного в класс-друг
T не имеют особого доступа к T
вложенный класс имеет такой же
доступ, как и охватывающий класс
CWG 500 C++98 класс-друг T не может наследовать закрытые или
защищённые члены T, но его вложенный класс может
оба могут наследовать
такие члены
CWG 1439 C++98 правило, касающееся объявлений друзей в нелокальных
классах, не охватывало объявления шаблонов
охватывает
CWG 1477 C++98 имя, впервые объявленное в объявлении друга внутри класса
или шаблона класса, не было видимо при поиске, если соответствующее
объявление предоставлено в другой области пространства имён
в этом случае оно
видимо при поиске
CWG 1804 C++98 если член шаблона класса объявлен другом, соответствующий
член специализаций частичных специализаций шаблона класса
не являлся другом класса, предоставившего дружбу
такие члены
также являются друзьями
CWG 2379 C++11 объявления друзей, ссылающиеся на полные специализации
шаблонов функций, могли объявляться constexpr
запрещено
CWG 2588 C++98 связывание имён, вводимых объявлениями друзей, было неясным прояснено

Ссылки

  • Стандарт C++23 (ISO/IEC 14882:2024):
    • 11.8.4 Друзья [class.friend]
    • 13.7.5 Друзья [temp.friend]
  • Стандарт C++20 (ISO/IEC 14882:2020):
    • 11.9.3 Друзья [class.friend]
    • 13.7.4 Друзья [temp.friend]
  • Стандарт C++17 (ISO/IEC 14882:2017):
    • 14.3 Друзья [class.friend]
    • 17.5.4 Друзья [temp.friend]
  • Стандарт C++14 (ISO/IEC 14882:2014):
    • 11.3 Друзья [class.friend]
    • 14.5.4 Друзья [temp.friend]
  • Стандарт C++11 (ISO/IEC 14882:2011):
    • 11.3 Друзья [class.friend]
    • 14.5.4 Друзья [temp.friend]
  • Стандарт C++98 (ISO/IEC 14882:1998):
    • 11.3 Друзья [class.friend]
    • 14.5.3 Друзья [temp.friend]

См. также

Типы классов определяют типы, содержащие несколько элементов данных
Спецификаторы доступа определяют видимость членов класса

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/friend

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API