저번에 연산자 오버로딩 한 다음에 3가지의 프렌드 기능 중에 프렌드 함수만 배웠다. 이번엔 나머지를 알아보겠다. 클래스도 프렌드가 될 수 있는데, 이 경우에는 프랜드 클래스의 모든 메서드는 오리지널 클래스의 private 멤버와 protected 멤버에 접근할 수 있다. 또한 어떤 클래스의 특정 멤버들만 프렌드 함수가 되도록 할 수 있다.
프렌드는 언제 사용할까? 예를 들면 TV와 리모콘의 관계일 때 사용하는 게 좋다. 둘은 무슨 관계인가? 리모콘이 TV를 포함하지도, 그 반대도 아니다. 그래서 상속은 좀 애매하다. TV는 리모콘에 대하여 아무 것도 할 수 없지만, 리모콘은 TV의 모든 것을 변경할 수 있다. 이럴 때 프렌드를 사용한다. 이에 관련된 코드다.
class Tv
{
public:
friend class Remote; // 선언 방법.
private:
void OnOff();
};
class Remote
{
private:
int mode;
public:
void OnOff(Tv& t) { t.OnOff(); } // Tv 메서드 사용하기.
};
프렌드 선언은 클래스 정의 내 public, protected, private 아무 곳이나 가능하고 차이도 없다. 하지만 가독성상 맨 위에 적는 게 좋겠다.
이렇게 생각하면 편하다. 만약 친구가 자신의 집에 놀러오라고 했다면, 그 뜻은 '우리 집에 있는 거 아무거나 편하게 써!'를 의미한다. (나만 그런가?) 그러니 Tv 클래스에 friend class Remote를 썼으니 Tv가 'Remote야, 내 클래스에 아무거나 써!'라는 의미랑 동일하다. 혹여나 클래스 외부에서 쓸 수 있다고 오해하지 말자. 클래스 내부에서만 가능하다.
원하는 메서드들만 다른 클래스에 대해 프렌드로 만드는 것도 가능하다. 그러면 그 메서드만 프렌드 클래스의 private에 접근 가능하고, 나머지는 불가능하다. 프렌드 멤버 함수를 만들어보겠다.
class Tv
{
friend void Remote::Set_chan(Tv & t, int c); // 프렌드 멤버 함수 선언.
};
클래스 프렌드와 마찬가지로 이용당할 클래스가 선언을 해준다. 이러면 Remote::Set_chan(Tv & t, int c) 메서드만 유일하게 Tv클래스의 private를 막 이용할 수 있다. 근데 컴파일러가 이 구문을 사용하려면, Remote가 누군지 알아야 한다. 따라서 Remote 클래스가 앞에 와야 한다. 근데 Remote 클래스를 프렌드 멤버 함수로 선정한다는 것은, Tv 멤버들 막 이용한다는 거 아니었나? Remote 클래스가 앞에 있으니까 그럼 클래스 내에서 Tv 멤버 사용할 때는 또 어떻게 해야 하나? 컴파일러가 Tv를 모를텐데. 그럼 Tv가 앞에 와야 하나? 이런 현상이 반복 된다. 이런 문제를 해결하기 위해서 전방 선언이 나왔다. 이것은 마치 함수 선언과 매우 비슷하다.
class Tv; // Remote가 클래스임을 알려줘야 프렌드 멤버 함수 선언 가능.
class Remote
{
...
};
class Tv
{
friend void Remote::Set_Chan(Tv & t, int c); // 프렌드 멤버 함수 선언.
};
그럼 아까전에 프렌드 클래스 할 때는 Tv가 먼저 왔는데 왜 됐냐고? → class를 앞에 붙여서 class라고 인식하기 때문이다. 그리고 저거 순서 바꾸면 안되나?
class Remote;
class Tv
{
friend void Remote::Set_Chan(Tv & t, int c); // 프렌드 멤버 함수 선언.
};
class Remote
{
...
};
안된다. 왜? 클래스 Tv에서 Remote 클래스의 함수에 대해서 프렌드 선언할 때, 그 함수 명을 컴파일러가 알고 있어야 하기 때문이다.
근데 여기서 또 다른 문제가 생긴다. 원래 순서에서 Remote 클래스가 인라인 코드를 가지고 있으면 어떡하나?
class Tv;
class Remote
{
void OnOff(Tv & t) {t.OnOff();}
};
class Tv
{
friend void Remote::Set_Chan(Tv & t, int c); // 프렌드 멤버 함수 선언.
};
가운데 끼인 Remote는 Tv 클래스에 대한 정의는 알아도, 아까 전의 Remote 메서드의 상황처럼 OnOff 메서드에 대한 정보는 모른다. 함수 정의가 cpp에 있으면 그때는 Tv 클래스의 정보를 알테지만, 이렇게 클래스 내에 정의돼있으면 어떡하나? 그냥 cpp에 적으면 되나? → 선언만 클래스에 하고 Tv 클래스 뒤에 inline 키워드를 사용해 인라인 메서드로 사용 가능하다.
class Tv;
class Remote
{
void OnOff(Tv & t); // 선언만.
};
class Tv
{
friend void Remote::Set_Chan(Tv & t, int c); // 프렌드 멤버 함수 선언.
};
inline void Remote::OnOff(Tv & t) {t.OnOff();} // 정의는 Tv 클래스 밑에.
두 클래스를 서로 프렌드 관계로 만들 수 있다. 이를 상호 프렌드라고 부른다. 아까 코드에서 추가해보자. Remote 클래스가 Tv의 프렌드였으니, 이번엔 Tv를 Remote의 프렌드로 만들면 된다.
class Tv
{
friend class Remote; // 프렌드 클래스 선언. 'class'문구로 인식 가능.
public:
void Buzz(Remote & r); // Remote 클래스는 알지만, 그 멤버는 알지 못한다.
...
};
class Remote
{
friend class Tv;
public:
void bool VolUp(Tv & t) {t.VolUp();} // Tv 클래스가 나와서 inline 함수 사용 가능.
...
};
inline void Tv::Buzz(Remote & r)
{
...
}
주의 할 점은, 전과 같이 하나의 클래스가 다른 클래스의 정보를 모를 수 있으므로, 인라인 함수로 선언할거면 뒤에다가, 아니면 cpp에다가 해야 한다.
때로는 하나의 함수가 서로 다른 두 개의 클래스 멤버에 접근해야 할 때도 있다. 그럴 때 두 클래스 모두 프렌드로 만들 수도 있다. 예를 들어 이런 식이다.
class Analyzer; // 전방 선언.
class Probe
{
friend void sync(Analyzer & a, const Probe & p);
friend void sync(Probe & a, const Analyzer & p);
...
};
class Analyer
{
friend void sync(Analyzer & a, const Probe & p);
friend void sync(Probe & a, const Analyzer & p);
...
};
inline void sync(Analyzer & a, const Probe & p)
{
...
}
inline void sync(Probe & a, const Analyzer & p)
{
}
마찬가지로 inline 말고 cpp에 함수 정의를 써도 된다.