Spec-Zone.ru › C++

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

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

Синтаксис

friend функция-объявление (1)
friend определение-функции (2)
friend расширенное-указание-класса ; (3)
friend простой-тип-указатель ;

friend указатель-типа ;

(4) (с C++11)

Описание

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) Определяет класс, структуру или объединение, указанные с помощью расширенного-указателя-класса (см. указатель-типа с расширением), как друга этого класса. Это означает, что объявления и определения членов друга могут получить доступ к закрытым и защищенным членам этого класса, а также что друг может унаследовать закрытые и защищенные члены этого класса. Имя класса, используемого в этом friend объявлении, не обязательно должно быть предварительно объявлено.
4) Определяет тип, указанный с помощью простого-указателя-типа или указателя-типа, как друга данного класса, если этот тип является (возможно, cv-квалифицированным) классом, структурой или объединением; в противном случае объявление friend игнорируется. Это объявление не будет объявлять новый тип.
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)
};
 
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
};

Примечания

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

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

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

Спецификаторы доступа не влияют на значение объявлений друзей (они могут появляться в 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), если не предоставлено соответствующее объявление в области пространства имён — см. пространства имён для получения дополнительной информации.

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

Как объявления шаблонов функций, так и объявления шаблонов классов могут появиться со спецификатором 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, который может быть членом-функцией или членом-типом (тип должен использовать расширенный-указатель-типа). Такое объявление корректно только в том случае, если последняя составляющая в его спецификаторе-имени-вложенного (имя слева от последнего ::) — это идентификатор простого шаблона (имя шаблона, за которым следует список аргументов в угловых скобках), который указывает на шаблон класса. Параметры шаблона такого объявления шаблона-друга должны быть выводимы из идентификатора простого шаблона.

В этом случае членом любого специализированного класса 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';
}

Пример

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

#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
запрещено

Ссылки

  • Стандарт C++23 (ISO/IEC 14882:2023):
    • 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/w/cpp/language/friend

Spec-Zone.ru

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