Friend declaration
friend 선언은 클래스 본문 에 나타나며, friend 선언이 나타나는 클래스의 private 및 protected 멤버에 대한 접근 권한을 함수나 다른 클래스에 부여합니다.
목차 |
구문
friend
함수 선언
|
(1) | ||||||||
friend
함수 정의
|
(2) | ||||||||
friend
상세 타입 지정자
;
|
(3) | (C++26 이전) | |||||||
friend
단순 타입 지정자
;
|
(4) |
(C++11 이후)
(C++26 이전) |
|||||||
friend
friend 타입 지정자 목록
;
|
(5) | (C++26 이후) | |||||||
| 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
목록, 각 지정자 뒤에는 생략 부호(
...
)가 올 수 있음
|
설명
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 };
템플릿 프렌드
function template
과
class template
선언은 모두
friend
지정자를 사용하여 비-로컬 클래스나 클래스 템플릿 내에서 나타날 수 있습니다(단, 함수 템플릿만이 우정을 부여하는 클래스나 클래스 템플릿 내에서 정의될 수 있습니다). 이 경우, 템플릿의 모든 특수화는 암시적으로 인스턴스화되거나, 부분적으로 특수화되거나, 명시적으로 특수화되는지 여부에 관계없이 friend가 됩니다.
class A { template<typename T> friend class B; // 모든 B<T>는 A의 friend입니다 template<typename T> friend void f(T) {} // 모든 f<T>는 A의 friend입니다 };
friend 선언은 부분 특수화를 참조할 수 없지만, 완전 특수화는 참조할 수 있습니다:
template<class T> class A {}; // 기본 템플릿 template<class T> class A<T*> {}; // 부분 특수화 template<> class A<int> {}; // 완전 특수화 class X { template<class T> friend class A<T*>; // 오류 friend class A<int>; // 정상 };
friend 선언이 함수 템플릿의 완전 특수화를 참조할 때, inline , constexpr (since C++11) , consteval (since C++20) 키워드와 기본 인수는 사용할 수 없습니다:
template<class T> void f(int); template<> void f<int>(int); class X { friend void f<int>(int x = 1); // 오류: 기본 인자는 허용되지 않음 };
템플릿 friend 선언은 클래스 템플릿 A의 멤버를 지정할 수 있으며, 이는 멤버 함수 또는 멤버 타입(해당 타입은
elaborated-type-specifier
를 사용해야 함)이 될 수 있습니다. 이러한 선언은 nested-name-specifier의 마지막 구성 요소(마지막
::
의 왼쪽에 있는 이름)가 클래스 템플릿을 지정하는 simple-template-id(템플릿 이름 뒤에 꺾쇠 괄호 안의 인수 목록)인 경우에만 올바른 형식을 가집니다. 이러한 템플릿 friend 선언의 템플릿 매개변수는 simple-template-id로부터 추론 가능해야 합니다.
이 경우, A의 임의의 특수화 또는 A의 부분 특수화의 멤버가 friend가 됩니다. 이는 기본 템플릿 A나 A의 부분 특수화를 인스턴스화하는 것을 포함하지 않습니다: 유일한 요구사항은 해당 특수화로부터 A의 템플릿 매개변수 추론이 성공해야 하며, 추론된 템플릿 인수를 friend 선언에 대입했을 때 해당 특수화의 멤버 유효한 재선언을 생성할 수 있는 선언이어야 한다는 것입니다:
// 기본 템플릿 template<class T> struct A { struct B {}; void f(); struct D { void g(); }; T h(); template<T U> T i(); }; // 완전 특수화 template<> struct A<int> { struct B {}; int f(); struct D { void g(); }; template<int U> int i(); }; // 다른 완전 특수화 template<> struct A<float*> { int *h(); }; // 클래스 템플릿 A의 멤버들에게 friend 권한을 부여하는 비템플릿 클래스 class X { template<class T> friend struct A<T>::B; // 모든 A<T>::B는 friend입니다. A<int>::B를 포함합니다 template<class T> friend void A<T>::f(); // A<int>::f()는 시그니처가 일치하지 않으므로 friend가 아니지만, // 예를 들어 A<char>::f()는 friend입니다 // template<class T> // friend void A<T>::D::g(); // 잘못된 형식입니다. 중첩 이름 지정자의 마지막 부분인 // // A<T>::D::의 D가 단순 템플릿 ID가 아닙니다 template<class T> friend int* A<T*>::h(); // 모든 A<T*>::h는 friend입니다: // A<float*>::h(), A<int*>::h() 등 template<class T> template<T U> // 모든 A<T>::i()와 A<int>::i()의 인스턴스화는 friend이며, friend T A<T>::i(); // 따라서 해당 함수 템플릿의 모든 특수화도 friend입니다 };
|
기본 템플릿 인수 는 선언이 정의이고 이 번역 단위에 이 함수 템플릿의 다른 선언이 나타나지 않는 경우에만 템플릿 friend 선언에서 허용됩니다. |
(C++11부터) |
템플릿 프렌드 연산자
템플릿 friend의 일반적인 사용 사례는 클래스 템플릿에 작용하는 비멤버 연산자 오버로드의 선언입니다, 예를 들어 operator << ( std:: ostream & , const Foo < T > & ) 와 같은 연산자를 사용자 정의 Foo < T > 에 대해 선언하는 경우입니다.
이러한 연산자는 클래스 본문 내에서 정의될 수 있으며, 이는 각각의
T
에 대해 별도의 비템플릿
operator
<<
를 생성하는 효과가 있고, 해당 비템플릿
operator
<<
를
Foo
<
T
>
의 friend로 만듭니다:
#include <iostream> template<typename T> class Foo { public: Foo(const T& val) : data(val) {} private: T data; // 이 T에 대한 비템플릿 operator<< 생성 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
>
내의 friend 선언은 해당
T
에 대한
operator
<<
의 완전 특수화를 참조할 수 있습니다:
#include <iostream> template<typename T> class Foo; // 함수 선언을 위해 전방 선언 template<typename T> // 선언 std::ostream& operator<<(std::ostream&, const Foo<T>&); template<typename T> class Foo { public: Foo(const T& val) : data(val) {} private: T data; // 이 특정 T에 대한 완전 특수화를 참조 friend std::ostream& operator<< <> (std::ostream&, const Foo&); // 참고: 이는 선언에서의 템플릿 인수 추론에 의존함 // "operator<< <T>"로 템플릿 인수를 지정할 수도 있음 }; // 정의 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'; }
링키지
스토리지 클래스 지정자 는 friend 선언에서 허용되지 않습니다.
|
함수 또는 함수 템플릿이 friend 선언에서 처음 선언되고 정의되며, 둘러싸는 클래스가 내보내기 선언 내에서 정의된 경우, 해당 이름은 둘러싸는 클래스의 이름과 동일한 링크를 가집니다. |
(since C++20) |
만약 (C++20 이전) 그렇지 않으면, 만약 (C++20 이후) 함수나 함수 템플릿이 friend 선언에서 선언되고, 해당하는 non-friend 선언 이 도달 가능한 경우, 해당 이름은 이전 선언에서 결정된 링크를 갖습니다.
그렇지 않으면, friend 선언에 의해 도입된 이름의 링크는 일반적인 방식으로 결정됩니다.
참고 사항
친구 관계는 이행적이지 않습니다 (당신 친구의 친구는 당신의 친구가 아닙니다).
친구 관계는 상속되지 않습니다 (친구의 자녀는 당신의 친구가 아니며, 당신의 친구는 당신 자녀의 친구가 아닙니다).
접근 지정자 는 friend 선언의 의미에 영향을 주지 않습니다 (이들은 private : 섹션이나 public : 섹션에 나타날 수 있으며, 차이가 없습니다).
friend 클래스 선언은 새로운 클래스를 정의할 수 없습니다 ( friend class X { } ; 는 오류입니다).
지역 클래스가 한정되지 않은 함수나 클래스를 friend로 선언할 때, 가장 안쪽의 비클래스 범위에 있는 함수와 클래스만 조회되며 , 전역 함수는 조회되지 않습니다:
class F {}; int f(); int main() { extern int g(); class Local // main() 함수 내의 지역 클래스 { friend int f(); // 오류: main() 내에 선언된 해당 함수 없음 friend int g(); // 정상: main() 내에 g 선언 존재 friend class F; // 지역 F와 friend 관계 설정(나중에 정의됨) friend class ::F; // 전역 F와 friend 관계 설정 }; class F {}; // 지역 F }
클래스나 클래스 템플릿
X
내부의 friend 선언에서 처음 선언된 이름은
X
의 가장 안쪽에 둘러싸인 네임스페이스의 멤버가 되지만,
(
X
를 고려하는 인수 종속 lookup을 제외하고) 네임스페이스 범위에서 일치하는 선언이 제공되지 않는 한
lookup에 보이지 않습니다. 자세한 내용은
namespaces
를 참조하십시오.
| 기능 테스트 매크로 | 값 | 표준 | 기능 |
|---|---|---|---|
__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
의 friend 클래스에 중첩된 클래스의 멤버들이
T
에 대한 특별한 접근 권한을 갖지 않음
|
중첩 클래스는 외부 클래스와
동일한 접근 권한을 가짐 |
| CWG 500 | C++98 |
T
의 friend 클래스는
T
의 private 또는
protected 멤버로부터 상속받을 수 없지만, 그 중첩 클래스는 상속받을 수 있음 |
둘 다 해당 멤버로부터
상속받을 수 있음 |
| CWG 1439 | C++98 |
비지역 클래스에서 friend 선언을 대상으로 하는 규칙이
템플릿 선언을 포함하지 않음 |
포함됨 |
| CWG 1477 | C++98 |
클래스나 클래스 템플릿 내의 friend 선언에서 처음 선언된 이름은
일치하는 선언이 다른 네임스페이스 범위에서 제공되는 경우 조회에 보이지 않음 |
이 경우 조회에
보이게 됨 |
| CWG 1804 | C++98 |
클래스 템플릿의 멤버를 friend로 선언할 때,
해당 클래스 템플릿의 부분 특수화의 특수화에 해당하는 멤버는 friendship을 부여하는 클래스의 friend가 아님 |
해당 멤버들도
friend임 |
| CWG 2379 | C++11 |
함수 템플릿의 완전 특수화를 참조하는 friend 선언을
constexpr로 선언할 수 있었음 |
금지됨 |
| CWG 2588 | C++98 | friend 선언으로 도입된 이름들의 링크age가 불명확했음 | 명확해짐 |
참조문헌
- C++23 표준 (ISO/IEC 14882:2024):
-
- 11.8.4 Friends [class.friend]
-
- 13.7.5 Friends [temp.friend]
- C++20 표준(ISO/IEC 14882:2020):
-
- 11.9.3 Friends [class.friend]
-
- 13.7.4 Friends [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 Friends [class.friend]
-
- 14.5.3 Friends [temp.friend]
참고 항목
| Class types | 여러 데이터 멤버를 보유하는 타입을 정의함 |
| Access specifiers | 클래스 멤버의 가시성을 정의함 |