C++ Friend 기능의 심층 분석

Friend는 C++에서 논란이 있으면서도 실용적인 가치를 지닌 기능이다.

1. 언어 설계 철학의 차이

C#에서는 private 멤버에 대한 접근이 해당 클래스 내부에서만 허용된다. 하지만 C++은 다른 접근 방식을 취한다:

캡슐화는 절대적 차단이 아닌, 접근 권한의 세밀한 제어를 의미한다.

C++에서는 특정 함수나 클래스가 외부에 있더라도 신뢰할 수 있는 경우, 이를 허용할 수 있다. 이것이 바로 friend의 핵심 개념이다.

2. Friend 함수 예제

class Sample {
private:
    int value = 10;

    friend void displayValue(const Sample& s);
};

void displayValue(const Sample& s) {
    std::cout << s.value << std::endl; // 접근 가능
}
  • displayValue는 멤버 함수가 아님에도 불구하고 Sample의 private 멤버에 접근 가능하다.
  • 이는 Sample 클래스에서 명시적으로 허용했기 때문이다.

3. Friend 클래스

class Vehicle;

class Controller {
public:
    void adjustSpeed(Vehicle& v);
};

class Vehicle {
private:
    int speed = 50;

    friend class Controller;
};

void Controller::adjustSpeed(Vehicle& v) {
    v.speed = 100; // 접근 가능
}

위 코드에서 Controller 클래스의 모든 메서드는 Vehicle의 private 멤버에 접근할 수 있다.

4. 상속과의 구분

friend는 다음과 같은 특성을 가진다:

  • 상속 관계가 아니다.
  • 멤버 함수가 아니다.
  • 양방향이 아니다.
class Alpha {
    friend class Beta;
};

위 코드는 BetaAlpha의 private 멤버에 접근 가능함을 의미하지만, 그 반대는 성립하지 않는다.

5. 컴파일러 관점에서의 본질

C++의 접근 제어는 런타임이 아닌 컴파일 타임에 처리된다:

접근 가능 여부 판단 조건:
    - 현재 함수가 클래스의 멤버인가?
    - 현재 함수가 해당 클래스의 friend로 선언되었는가?

즉, friend는 단순히 접근 가능한 함수 집합을 확장하는 것이며, 실행 시 오버헤드는 전혀 없다.

6. 존재 이유

C++은 다음과 같은 목표를 추구하기 때문에 friend가 필요하다:

  • 제로 오버헤드 추상화
  • 고성능 보장
  • 세밀한 제어력 제공

7. 실제 활용 사례

(1) 연산자 오버로딩

class Point {
private:
    double x, y;

public:
    Point(double px, double py) : x(px), y(py) {}

    friend Point operator+(const Point& p1, const Point& p2);
};

Point operator+(const Point& p1, const Point& p2) {
    return Point(p1.x + p2.x, p1.y + p2.y);
}

비멤버 연산자로 정의해야 하는 경우, friend 없이는 좌측 피연산자의 타입에 제약이 생긴다.

(2) 팩토리 함수

class Resource {
private:
    Resource() {} // 생성자 비공개

public:
    friend Resource createResource();
};

Resource createResource() {
    return Resource(); // friend 함수는 생성 가능
}

외부에서는 객체를 직접 생성할 수 없지만, 특정 함수를 통해 생성하도록 제한할 수 있다.

(3) 컨테이너와 반복자

template<typename T>
class Container {
private:
    struct Node { T data; Node* next; };
    Node* head;

    friend class Iterator;
};

template<typename T>
class Container<T>::Iterator {
public:
    T& operator*() {
        return current->data; // 내부 노드에 접근 가능
    }
};

반복자는 컨테이너의 내부 구조에 접근해야 하므로, friend로 선언되어야 한다.

(4) PImpl (Pointer to Implementation)

class GraphicsEngine {
private:
    class EngineImpl;
    EngineImpl* impl;

    friend class EngineImpl;
};

구현 클래스가 외부 인터페이스 클래스의 멤버에 접근해야 할 때 유용하다.

8. C#에서는 왜 필요 없는가?

  • internal: 어셈블리 내에서만 접근 가능
  • partial: 여러 파일에 걸쳐 클래스 정의 가능
  • 어셈블리 단위의 접근 제어

C++은 클래스 단위의 캡슐화만 지원하므로, 더 세밀한 제어가 필요한 경우 friend를 사용한다.

9. 위험성과 주의사항

  • 캡슐화 경계를 무너뜨릴 수 있음
  • 불필요한 결합도 증가
  • 유지보수 난이도 상승

많은 코딩 표준에서 friend 사용을 자제하라고 하지만, STL 등에서도 널리 사용되고 있다.

10. 좀 더 깊은 이해

C++의 핵심 철학 중 하나는 다음과 같다:

클래스는 보안 경계가 아니라 추상화 경계이다.

friend는 서로 관련된 여러 클래스가 하나의 논리적 모듈을 구성할 수 있도록 해주는 도구이다.

11. 고급 응용 예제

class ThreadMutex {
private:
    bool locked = false;

    friend class ScopedLock;
};

class ScopedLock {
public:
    explicit ScopedLock(ThreadMutex& mtx) {
        mtx.locked = true;
    }

    ~ScopedLock() {
        mtx.locked = false;
    }
};

뮤텍스 상태를 직접 변경해야 하는 RAII 스타일의 락 가드 클래스는 friend로 선언되어야 한다.

12. 설계 철학 요약

C++ 창시자 Bjarne Stroustrup의 철학은 다음과 같다:

"프로그래머가 무엇을 하고 있는지 안다면, 그 권한을 줘야 한다."

friend는 이러한 신뢰 기반의 언어 설계를 구현하는 방법이다.

13. 요약

friend는:

  • 컴파일 타임 접근 권한 부여 메커니즘
  • private 멤버 접근 범위 확장 도구
  • 캡슐화 파괴자가 아님

14. 고민해볼 문제

다음 두 클래스가 있다고 가정하자:

class Matrix;
class MatrixOperations;

MatrixOperationsMatrix의 friend인 경우, 이는 객체지향적인 설계인가? 아니면 모듈 중심 설계인가?

이 질문에 답할 수 있다면, 당신은 이미 C++의 설계 철학을 이해한 것이다.

태그: C++ friend class-design access-control STL

6월 3일 16:32에 게시됨