본문으로 건너뛰기

안동민 개발노트

본문 시작

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

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

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

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

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

friend는 private 경계에 여는 좁은 문이다

외부 코드 전체를 믿는 문법이 아니라, 선언한 함수나 클래스 하나에만 내부 접근권을 부여합니다.

  1. class Box

    공개 API는 불변식을 지키는 정문입니다. private width , height 는 외부에서 직접 읽지 못합니다.

  2. private

    width , height 는 외부에서 직접 읽지 못합니다.

  3. 허용 대상

    area(box) 멤버 함수가 아니지만 지정된 계산에 필요한 내부 값만 읽습니다.


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&) constto_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 권한은 선언한 대상에만 열린다

operator<< 가 내부 값을 읽을 수 있어도 그 권한은 다른 함수, 상속 계층, 반대 방향으로 퍼지지 않습니다.

  1. not transitive

    A가 B의 friend여도 B의 friend가 A의 friend가 되지 않습니다.

  2. not inherited

    기반 클래스 friend 권한이 파생 클래스 private까지 확장되지 않습니다.

  3. not symmetric

    A가 B를 열어 주어도 B가 A를 자동으로 열어 주지는 않습니다.


중요한 특성: 상속/전이되지 않음

friend 관계는 직관과 달리 전이되지 않습니다.

  • A가 B의 friend라고 해서, B의 friend인 C가 A의 friend가 되지는 않습니다.
  • 기반 클래스의 friend가 파생 클래스의 private 멤버까지 접근할 수 있는 것은 아닙니다.
  • friend로 지정된 클래스의 파생 클래스도 권한을 자동 상속받지 않습니다.
  • friend 선언은 양방향이 아닙니다.

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


friend 사용 시 주의사항

캡슐화 약화

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

결합도 증가

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

최소 권한 원칙

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

friend는 마지막에 선택하는 최소 권한 계약이다

내부 접근이 필요해 보여도 먼저 공개 API와 멤버 함수로 표현할 수 있는지 확인한 뒤, 필요한 만큼만 엽니다.

  1. 01

    public API 읽기 의미를 이름 있는 함수로 노출할 수 있는가?

  2. 02

    member 객체 불변식을 지키며 내부 책임으로 처리 가능한가?

  3. 03

    non-member private 접근 없이 외부 계산으로 충분한가?

  4. 04

    friend function 함수 하나에만 예외 접근을 줄 수 있는가?

  5. 05

    friend class 여러 멤버가 꼭 같은 내부 권한을 공유해야 하는가?

  6. 캡슐화 약화

    외부 코드가 내부 구조를 알수록 클래스 변경 비용이 커집니다.

  7. 결합도 증가

    friend 대상과 private 필드명이 함께 묶여 리팩터링이 어려워집니다.

  8. 범위 과다

    friend class 는 대상 클래스의 모든 멤버 함수에 문을 엽니다.


대체 설계는 없는가?

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

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

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


friend 사용 원칙 요약

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

아래 결정 카드는 그 판단 순서를 정리한 것입니다.

friend는 “왜 열어야 하는가”가 설명될 때만 쓴다

판단은 공개 API 가능성, 좌측 피연산자, 권한 범위, 문서화 여부 순서로 좁혀 갑니다.

  1. 먼저 거절

    공개 함수로 의미를 표현할 수 있다 getter 남발이 아니라 이름 있는 관찰자와 멤버 함수가 우선입니다.

  2. 검토 시작

    좌측 피연산자가 내 타입이 아니다 std::ostream << obj 처럼 멤버 함수 후보가 어색한지 봅니다.

  3. 범위 축소

    함수 하나로 충분한가 충분하면 friend function , 아니면 마지막에 friend class 를 검토합니다.

  4. 문서화

    권한 이유가 코드 옆에서 읽힌다 왜 private 접근이 필요한지 주변 코드만 보고 납득되어야 합니다.

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

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