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;
};
위 코드는 Beta가 Alpha의 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;
MatrixOperations가 Matrix의 friend인 경우, 이는 객체지향적인 설계인가? 아니면 모듈 중심 설계인가?
이 질문에 답할 수 있다면, 당신은 이미 C++의 설계 철학을 이해한 것이다.