본문 바로가기

프렌드 함수와 프렌드 클래스

friend로 특정 외부 함수나 클래스에 내부 접근을 허용하는 방법과 캡슐화를 지키는 제한적 사용 원칙을 익힙니다.

이전 절 ‘연산자 오버로딩 기초’에서 friend 키워드를 간단히 사용했습니다.

이번 절에서는 friend 문법 자체를 독립적으로 다룹니다.

friend는 캡슐화를 일부 우회하는 문법이므로, 정확한 용도와 한계를 알고 사용하는 것이 중요합니다.

C++ · FRIEND ACCESS

friend는 지정한 함수나 클래스에 비공개 접근 권한을 준다

본문 Box가 area를 friend로 선언하면 비멤버 area는 width와 height를 읽어 12를 계산합니다. 접근 권한과 객체 상태의 올바른 사용은 별도로 설계해야 합니다.

일반 공개 API와 지정된 friend의 서로 다른 접근원desktop3노드2방향접근간선이다.일반호출자는Box공개API로접근하며friendarea는Boxprivatewidth3height4를읽는다.화살표는각각Box로향하는접근관계이고위→아래시간순서가아니다.결과12/constBox&와nonmember를보존한다.일반 호출자public API만 사용private 직접 이름 불가Box private 상태width3 · height4friend areaconst Box& 전달3×4 = 12화살표: 대상 상태의 접근 경로friend는 비멤버암시적 this 없음
Box 공개 API와 지정된 friend 함수의 접근일반 호출자는 공개 API로 접근한다. 지정된 비멤버 area는 Box private width 3과 height 4에 접근해 12를 반환한다. 화살표는 접근 경로이며 실행 순서가 아니다.일반 호출자public API로 접근private 직접 접근 불가Box · private 상태width = 3height = 4friend area(const Box&)b.width * b.height본문 결과: 12

일반 호출자 → Box

공개 API를 사용합니다. private 멤버 이름을 직접 사용할 수 없습니다.

friend area → Box

지정된 area는 비멤버 함수지만 width=3, height=4를 읽어 12를 계산합니다. 암시적 this는 없습니다.

friend 함수 · 좁은 권한 지정

특정 함수만 지정하면 그 함수에 private/protected 접근을 허용합니다. 함수는 여전히 비멤버입니다.

public API로 구현할 수 있다면 friend가 필요하지 않을 수 있습니다.

friend 클래스 · 권한 범위 확인

본문 SecretVault가 VaultInspector를 friend로 지정하면 그 클래스의 멤버에 접근 권한을 줍니다.

friend 관계는 대칭·전이·상속되지 않습니다. 한 클래스 전체에 주는 권한과 특정 함수에 주는 권한을 구분합니다.

friend는 접근 제어의 예외를 명시하는 도구입니다. private나 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() 같은 공개 멤버 함수로 분리할 수도 있습니다.

operator<< 프렌드 패턴
#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 선언은 양방향이 아닙니다.

즉, 정확히 선언한 대상에만 권한이 부여됩니다.

권한 선언에서 허용 여부를 판단하기

Box 원문 예제의 area는 friend이고 비멤버입니다. area의 선언과 정의에서 const Box&를 그대로 유지한 채 Box의 friend 선언만 지운 새 빌드가 성공하나요? 다른 조건으로 SecretVault가 VaultInspector를 friend로 선언하고, VaultInspector가 Helper를 friend로 선언했다고 가정합니다. Helper가 직접 vault.code를 읽을 권한도 생기나요? 선언한 대상과 접근하려는 클래스의 방향을 각각 적어 보세요.

각 접근의 근거

첫 조건은 area가 b.width·b.height를 직접 이름 붙이므로 private 접근 오류로 거부됩니다. 객체를 const 참조로 받는 것은 읽기 경로를 한정할 뿐 private 접근 권한을 만들지 않습니다. 둘째 조건도 Helper는 SecretVault가 허용한 대상이 아니므로 code에 직접 접근할 수 없습니다. VaultInspector의 friend 선언은 VaultInspector 내부 접근에 관한 별도 권한이며 SecretVault 권한을 전이하지 않습니다. public 관찰자만으로 구현하면 friend를 없앨 수 있지만, 이는 함수의 구현과 공개 계약을 함께 바꾸는 별도 설계입니다.


friend 사용 시 주의사항

캡슐화 약화

무분별한 friend는 클래스 내부 구현을 외부에 과도하게 노출합니다.

결합도 증가

friend 대상과의 의존성이 강해져 리팩터링이 어려워질 수 있습니다.

최소 권한 원칙

가능하면 friend class보다 friend function처럼 좁은 범위를 우선 고려합니다.


대체 설계는 없는가?

항상 friend가 정답은 아닙니다.

  • 읽기 전용 정보라면 불변식을 깨지 않는 의미 있는 관찰자 함수 제공
  • 조작이 필요하면 공개 인터페이스(멤버 함수)로 의도 명시
  • 출력 전용이라면 필요한 정보만 직렬화 함수로 제공

friend는 “공개 API로 표현하기 어려운 제한된 경우”에 선택하는 것이 보통 더 안전합니다.


friend 사용 원칙 요약

friend를 선택하기 전에는 공개 인터페이스로 해결할 수 있는지, 권한 범위가 너무 넓지 않은지 먼저 확인해야 합니다.

  • friend는 특정 외부 함수/클래스에 내부 접근 권한을 부여한다.
  • 연산자 오버로딩(operator<<)에서 매우 자주 사용된다.
  • 상속/전이/양방향이 아니므로 선언 대상은 명확하지만, friend class는 대상 내부의 접근 범위가 넓다.
  • 강력하지만 캡슐화를 약화시키므로 최소 범위로 제한해 사용해야 한다.

friend는 무조건 피해야 할 문법은 아니지만, 사용할 때마다 권한을 여는 이유와 범위를 문장으로 설명할 수 있어야 합니다.