프렌드 함수와 프렌드 클래스
friend로 특정 외부 함수나 클래스에 내부 접근을 허용하는 방법과 캡슐화를 지키는 제한적 사용 원칙을 익힙니다.
이전 절 ‘연산자 오버로딩 기초’에서 friend 키워드를 간단히 사용했습니다.
이번 절에서는 friend 문법 자체를 독립적으로 다룹니다.
friend는 캡슐화를 일부 우회하는 문법이므로, 정확한 용도와 한계를 알고 사용하는 것이 중요합니다.
C++ · ARCHITECTURE
friend는 캡슐화 경계의 좁은 예외다
공개 API는 모든 호출자에게 열리지만 friend 권한은 지정된 협력자에게만 비공개 접근을 허용한다.
- 일반 호출자 → 클래스
공개 API를 통해 불변식에 접근한다.
- 지정된 협력자 → 클래스
선언된 friend만 필요한 private 연산에 접근한다.
- 권한 경계
friend는 상속·대칭·전이되지 않는다.
공개 API는 모든 호출자에게 열리지만 friend 권한은 지정된 협력자에게만 비공개 접근을 허용한다.
friend란 무엇인가?
friend는 클래스 외부의 특정 함수 또는 클래스에게 해당 클래스의 private/protected 멤버 접근 권한을 부여하는 키워드입니다.
즉, “이 함수(또는 이 클래스)만 특별히 내부 구현에 접근하도록 허용”하는 선언입니다.
프렌드 함수 (friend function)
프렌드 함수는 클래스의 멤버 함수는 아니지만, 멤버처럼 내부 데이터에 접근할 수 있습니다.
#include <iostream>
class Box {
private:
int width;
int height;
public:
Box(int w, int h) : width(w), height(h) {}
// area 함수에 private 접근 권한 부여
friend int area(const Box& b);
};
int area(const Box& b) {
return b.width * b.height; // private 멤버 접근 가능
}
int main() {
Box b(3, 4);
std::cout << area(b) << "\n"; // 12
}프렌드 함수는 객체를 호출 주체(this)로 갖지 않으므로, 좌측 피연산자가 클래스 객체가 아닌 경우(예: std::cout << obj)에 특히 유용합니다.
프렌드 클래스 (friend class)
특정 클래스 전체에 접근 권한을 주고 싶을 때는 friend class를 사용합니다.
#include <iostream>
#include <string>
class SecretVault {
private:
std::string code{"A1B2C3"};
friend class VaultInspector; // VaultInspector는 private 접근 가능
};
class VaultInspector {
public:
void printCode(const SecretVault& vault) const {
std::cout << "Vault code: " << vault.code << "\n";
}
};
int main() {
SecretVault vault;
VaultInspector inspector;
inspector.printCode(vault);
}프렌드 클래스는 그 클래스의 모든 멤버 함수에 접근 권한을 줍니다.
그래서 권한 범위가 넓습니다.
특정 검사 함수 하나만 내부 접근이 필요하다면 friend void VaultInspector::check(const SecretVault&)처럼 특정 멤버 함수만 여는 설계도 검토할 수 있습니다.
operator<<와 프렌드의 관계
operator<<는 보통 다음 이유로 프렌드 함수로 선언됩니다.
- 좌측 피연산자가
std::ostream이므로 멤버 함수로 만들기 부적합 - 객체 내부 상태를 출력하려면
private접근이 필요한 경우가 많음
다만 출력 연산자가 항상 friend여야 하는 것은 아닙니다.
공개 관찰자 함수로 충분하면 비프렌드 operator<<를 만들 수 있고, 클래스가 출력 정책을 직접 제공해야 한다면 print(std::ostream&) const나 to_string() 같은 공개 멤버 함수로 분리할 수도 있습니다.
#include <iostream>
class Point {
private:
int x, y;
public:
Point(int _x, int _y) : x(_x), y(_y) {}
friend std::ostream& operator<<(std::ostream& os, const Point& p);
};
std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x << ", " << p.y << ")";
return os;
}이 패턴은 ‘연산자 오버로딩 기초’와 함께 이해하면 더 명확합니다.
다형성 계층(기반/파생 클래스)에서 출력 연산자를 설계할 때도 같은 원칙이 적용됩니다.
operator<<의 피연산자를 값(Base)으로 받으면 객체 슬라이싱이 발생할 수 있으므로, 보통 const Base&를 사용합니다.
단, 참조만으로 파생 타입별 출력이 자동으로 바뀌지는 않습니다.
실제 파생 타입의 출력 형식까지 다르게 하려면 기반 클래스에 virtual print(std::ostream&) const 같은 가상 함수가 필요합니다.
이 개념은 10장의 ‘다형성과 가상 함수’와 함께 보면 설계 의도가 더 분명해집니다.
중요한 특성: 상속/전이되지 않음
friend 관계는 직관과 달리 전이되지 않습니다.
- A가 B의 friend라고 해서, B의 friend인 C가 A의 friend가 되지는 않습니다.
- 기반 클래스의 friend가 파생 클래스의 private 멤버까지 접근할 수 있는 것은 아닙니다.
- friend로 지정된 클래스의 파생 클래스도 권한을 자동 상속받지 않습니다.
- friend 선언은 양방향이 아닙니다.
즉, 정확히 선언한 대상에만 권한이 부여됩니다.
friend 사용 시 주의사항
무분별한 friend는 클래스 내부 구현을 외부에 과도하게 노출합니다.
friend 대상과의 의존성이 강해져 리팩터링이 어려워질 수 있습니다.
가능하면 friend class보다 friend function처럼 좁은 범위를 우선 고려합니다.
대체 설계는 없는가?
항상 friend가 정답은 아닙니다.
- 읽기 전용 정보라면 불변식을 깨지 않는 의미 있는 관찰자 함수 제공
- 조작이 필요하면 공개 인터페이스(멤버 함수)로 의도 명시
- 출력 전용이라면 필요한 정보만 직렬화 함수로 제공
friend는 “공개 API로 표현하기 어려운 제한된 경우”에 선택하는 것이 보통 더 안전합니다.
friend 사용 원칙 요약
friend를 선택하기 전에는 공개 인터페이스로 해결할 수 있는지, 권한 범위가 너무 넓지 않은지 먼저 확인해야 합니다.
아래 결정 카드는 그 판단 순서를 정리한 것입니다.
friend는 특정 외부 함수/클래스에 내부 접근 권한을 부여한다.- 연산자 오버로딩(
operator<<)에서 매우 자주 사용된다. - 상속/전이/양방향이 아니므로 선언 대상은 명확하지만,
friend class는 대상 내부의 접근 범위가 넓다. - 강력하지만 캡슐화를 약화시키므로 최소 범위로 제한해 사용해야 한다.
friend는 무조건 피해야 할 문법은 아니지만, 사용할 때마다 권한을 여는 이유와 범위를 문장으로 설명할 수 있어야 합니다.