friend 선언
friend 선언
friend 선언은 클래스 본문에 나타나서, 함수나 다른 클래스에 이 클래스의 private·protected 멤버에 접근할 수 있는 권한을 부여해요. '친구(friend)'로 지정된 대상만 열어 주고 싶을 때 쓰는 기능입니다.
출처: cppreference
본문
문법
friend function-declaration (1)
friend function-definition (2)
friend elaborated-type-specifier ; (3) (until C++26)
friend simple-type-specifier ; friend typename-specifier ; (4) (since C++11) (until C++26)
friend friend-type-specifier-list ; (5) (since C++26)
- (1) 함수 friend 선언
- (2) 함수 friend 정의
- (3) ~ (5) 클래스 friend 선언
| function-declaration | - | 함수 선언 |
| function-definition | - | 함수 정의 |
| elaborated-type-specifier | - | elaborated 타입 지정자 |
| 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부터) 항상 inline이에요.
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)~(5) 클래스를 이 클래스의 친구로 지정해요. 즉 친구의 멤버 선언과 정의가 이 클래스의 private·protected 멤버에 접근할 수 있고, 친구가 이 클래스의 private·protected 멤버로부터 상속받을 수도 있다는 뜻이에요.
- (3) 클래스는
elaborated-type-specifier로 이름이 붙어요. 이 friend 선언에 쓰인 클래스 이름은 미리 선언되어 있지 않아도 됩니다. - (4) 클래스는
simple-type-specifier나typename-specifier로 이름이 붙어요. 이름이 붙은 타입이 클래스 타입이 아니면 이 friend 선언은 무시돼요. 이 선언은 새 타입을 전방 선언하지 않습니다. - (5)
friend-type-specifier-list의 모든 클래스를 이 클래스의 친구로 지정해요. 이름이 붙은 타입이 클래스 타입이 아니면 이 friend 선언에서 무시됩니다.
friend-type-specifier-list의 각 지정자는 줄임표가 뒤따르지 않으면 클래스 하나를 가리키고, 그렇지 않으면 팩 확장(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
};
템플릿 친구 (Template friends)
함수 템플릿과 클래스 템플릿 선언 모두 어떤 비지역 클래스나 클래스 템플릿에서 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
};
friend 선언은 부분 특수화를 가리킬 수 없지만, 전체 특수화(full specialization)는 가리킬 수 있어요:
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
};
friend 선언이 함수 템플릿의 전체 특수화를 가리킬 때는 inline, constexpr(C++11부터), consteval(C++20부터) 키워드와 기본 인자(default argument)를 쓸 수 없어요:
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
};
템플릿 friend 선언은 클래스 템플릿 A의 멤버를 가리킬 수 있는데, 그 멤버는 멤버 함수 또는 멤버 타입(타입은 elaborated-type-specifier를 써야 해요)일 수 있어요. 이런 선언은 nested-name-specifier의 마지막 구성 요소(마지막 :: 왼쪽의 이름)가 클래스 템플릿을 가리키는 simple-template-id(템플릿 이름 뒤에 꺾쇠 괄호 안의 인자 목록)일 때만 well-formed이에요. 이때 템플릿 friend 선언의 템플릿 매개변수는 simple-template-id에서 추론될 수 있어야 합니다.
이 경우 A의 어떤 특수화든 그 특수화의 멤버가 친구가 돼요. 이 과정에서 기본 템플릿 A나 A의 부분 특수화를 인스턴스화하지는 않습니다. 요구되는 것은 그 특수화에서 A의 템플릿 매개변수 추론이 성공하고, 추론된 템플릿 인자를 friend 선언에 대입했을 때 그 특수화의 멤버의 유효한 재선언이 되는 선언이 산출되는 것뿐이에요:
// 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
};
기본 템플릿 인자는 템플릿 friend 선언이 정의이고 이 함수 템플릿의 다른 선언이 이 번역 단위에 나타나지 않는 경우에만 허용돼요. (C++11부터)
템플릿 friend 연산자 (Template friend operators)
템플릿 friend의 흔한 용도는 클래스 템플릿에 작용하는 비멤버 연산자 오버로드를 선언하는 것이에요. 예를 들어 어떤 사용자 정의 Foo<T>에 대해 operator<<(std::ostream&, const Foo<T>&) 같은 거죠.
그런 연산자를 클래스 본문 안에서 정의하면 T마다 별도의 비템플릿 operator<<가 생성되는 효과가 있고, 그 비템플릿 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> 안의 friend 선언이 자기 T에 대한 operator<<의 전체 특수화를 가리킬 수 있어요:
#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';
}
연결 (Linkage)
friend 선언에는 저장 클래스 지정자(storage class specifier)를 쓸 수 없어요.
함수나 함수 템플릿이 friend 선언에서 처음 선언·정의되고, 둘러싼 클래스가 exporting declaration 안에서 정의된다면, 그 이름은 둘러싼 클래스의 이름과 같은 연결을 가져요. (C++20부터)
그렇지 않고(C++20 이전까지는 '그리고'), 함수나 함수 템플릿이 friend 선언에서 선언되고 대응하는 비friend 선언이 도달 가능(reachable)하면, 그 이름은 이전 선언에서 결정된 연결을 가져요.
그 외의 경우 friend 선언이 도입하는 이름의 연결은 평소대로 결정됩니다.
참고 사항
- 친구 관계는 전이되지 않아요(친구의 친구는 내 친구가 아닙니다).
- 친구 관계는 상속되지 않아요(친구의 자식은 내 친구가 아니고, 내 친구는 내 자식의 친구가 아님).
- 접근 지정자(access specifier)는 friend 선언의 의미에 영향을 주지 않아요(
private:섹션에든public:섹션에든 차이 없이 쓸 수 있습니다). - friend 클래스 선언은 새 클래스를 정의할 수 없어요(
friend class X {};는 오류). - 지역 클래스가 한정되지 않은(unqualified) 함수나 클래스를 친구로 선언하면, 전역 함수가 아니라 가장 안쪽의 비클래스 스코프에 있는 함수와 클래스만 조회돼요:
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 안의 friend 선언에서 처음 선언된 이름은 X를 둘러싼 가장 안쪽 네임스페이스의 멤버가 되지만, 네임스페이스 스코프에 일치하는 선언이 제공되지 않으면 조회에는 보이지 않아요(X를 고려하는 인자 종속 조회는 제외). 자세한 내용은 네임스페이스 문서를 참고하세요.
| 기능 테스트 매크로 | 값 | 표준 | 기능 |
| __cpp_variadic_friend | 202403L | (C++26) | Variadic friend 선언 |
키워드
friend
예제
스트림 삽입·추출 연산자는 흔히 비멤버 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
더 알아보기 (Learn more)
- 클래스 타입 (class types) — 여러 데이터 멤버를 담는 타입
- 접근 지정자 (access specifiers) — 클래스 멤버의 가시성
- 네임스페이스 (namespaces) — friend로 선언된 이름의 가시성 규칙