friend 키워드 (클래스)
클래스의 protected / private 의 접근 권한을 또 다른 클래스에 넘겨줄 수 있도록 하는 키워드
ex) 티비 와 리모콘 의 관계
티비와 리모콘은 is-a (상속) 관계도 아니고 has-a (컴포지션) 관계도 아니다.
-> 티비는 리모콘이 아니며 티비는 리모콘을 소유하고 있지도 않다.
두 클래스는 완전히 다른 개별적인 개체들이지만 리모콘은 티비의 기능을 조작하여 다룰 수 있다.
class TV {
friend TVRemote;
public:
void TVvolup() {
++vol;
}
private:
int vol;
};
class TVRemote {
public:
void volup() {
tv.TVvolup(); // 1 tv의 public 함수를 이용
++tv.vol; // 2 tv의 멤버변수를 직접 사용
}
private:
TV tv;
};
class AirconditionRemote {
void volup() {
tv.TVvolup();
}
private:
TV tv; // ??
};
리모컨을 이용하여 볼륨을 1올리는 코드를 작성한다 할 때,
1 -> tv의 public 함수를 이용하는 방법이 있을것이고
2 -> tv의 멤버변수에 직접 접근하여 더하는 방식도 있을것이다.
1의 방식같은경우는 TV의 멤버변수를 직접 건들지 않는다는 점에서 비교적 안전하지만 의도치 않게 AirConditionRemote 가 tv의 볼륨을 멋대로 제어할수 있는 문제점이 있다. (개발자의 실수로 인한)
2의 방식같은경우는 TV의 멤버변수를 직접 제어하는 방식인데 만약에 Remote 가 여러개 있다고 하면 하나의 TVvolup() 함수 대신 각각 Remote 내부에서 제어하기 때문에 나중에 유지보수 측면에서 힘들 수 있다.
(멤버 함수를 통해서만 객체 상태 변경이 가능하도록 하여 객체를 안전하게 유지해야 한다.)
그러면 TV의 멤버변수는 private 이고 볼륨 조절을 TVRemote에서만 할 수 있도록 하고싶을땐 어떡해야 할까?
-> friend 키워드를 이용해 해당 클래스만 접근 가능하도록 설정할 수 있다,
class TV {
friend TVRemote; // TVRemote 클래스는 TV의 protected / private 멤버 변수 함수에 접근 가능하다
private:
void TVvolup() {
++vol;
}
int vol;
};
class TVRemote {
public:
void volup(TV tv) {
++tv.vol; // friend 접근 가능
tv.TVvolup(); // friend 접근 가능
}
};
class AirconditionRemote {
void volup(TV tv) {
++tv.vol; // 에러
tv.TVvolup(); // 에러
}
};
friend 키워드 자체가 protected / priavte 에 접근할 수 있도록 선언하기 때문에 캡슐화 원칙을 위배하는 것이니 오남용하면 유지보수 측면에서 복잡성이 증가 할 수 있기때문에 필요한 경우에만 사용하도록 해야 함.
friend 키워드 (함수)
friend function 은 클래스의 멤버 함수가 아니지만 클래스 내에서 friend 키워드를 사용하여 클래스 내부에서 접근할 수 있도록 하는것이다.
class A {
public:
void AFunc(); // 클래스 A의 멤버 함수
friend void func(A &a); // 클래스 A의 멤버 함수 아님!! 외부함수임
private:
int num;
};
void A::AFunc() {
cout << num << endl;
}
void func(A &a) { // 외부함수 func에 한해서 A의 protected / private 접근 가능
cout << a.num << endl;
}
다음 두 코드는 같다. (둘 다 외부함수)
1. 외부함수인데 private인 a의 멤버변수 사용하기 위해 friend 키워드
class A {
public:
friend void func(A &a);
private:
int num;
};
void func(A &a) {
cout << a.num << endl;
}
2. 외부함수지만 클래스 내부에서 정의
class A {
public:
friend void func(A &a) { // 외부함수 func을 멤버함수처럼 클래스 A 내부에서 정의 (실제론 외부함수)
cout << a.num << endl;
}
private:
int num;
};
가장 많이드는 예시는 operator 오버로딩
class A {
public:
// 멤버함수 - 연산자의 왼쪽 피연산자가 객체 자신
A operator+(const A& a) const {
return A{ this->id + a.id };
}
// 전역함수 - 연산자의 왼쪽 피연산자가 ostream 객체
friend ostream& operator<<(ostream& os, const A& a)
{
os << "id: " << a.id;
return os;
}
int id;
};
// ----------------------------------------------------------------------------------
class A {
public:
friend ostream &operator<<(ostream& os, const A& a); // 전역함수
friend A operator+(const A& a1, const A& a2); // 전역함수
int id;
};
ostream &operator<<(ostream &os, const A &a) {
os << "id: " << a.id;
return os;
}
A operator+(const A& a1, const A& a2) {
return A { a1.id + a2.id };
}
컴파일러에서 함수를 호출할 때 본인 인스턴스의 주소(this) 를 같이 넘긴다
멤버함수 같은경우는 본인의 인스턴스 주소를 알기 때문에 더할 다른 인스턴스 (a) 만 알면되지만
전역함수 경우는 연살할 객체 모두 (a1, a2) 를 명시적으로 전달해야 한다.
그리고 위와 같이 동일한 자료형에 대해서 멤버함수와 전역함수로 동시에 오버로딩 했을 경우 멤버함수로 오버로딩된 함수가 우선시 되지만 이런 상황은 가급적 만들지 않는 것이 좋다.
operator + 경우는 멤버함수 연산자 오버로딩이고
operator<< 경우는 전역함수 연산자 오버로딩 이다.
operator + 처럼 멤버함수가 가능한 경우는
A a1{3};
A a2{4};
A a3 = a1 + a2;
a3 = a1.operator+(a2) 처럼 왼쪽 항이 자신의 객체라면 멤버함수로 호출가능하다.
cout << a1;
위의 코드는
cout.operator<<(a1)
cout 는 std 안의 ostream 클래스에 있는 객체이고
즉, ostream 클래스에 << 를 오버로딩해야 a1 을 출력할 수 있다.
ostream 클래스 멤버함수를 건드릴 수는 없으니, 전역함수를 통해 오버로딩 해야 한다.
연산자 | 멤버 함수 (class T 내부) | 전역 함수 (friend 또는 namespace 내) |
왼쪽 피연산자 - 자신 operator+, operator-, operator*, operator/ |
O (T + T) | O (T + int 등) |
대입 연산자 operator=, operator+=, operator-= |
O (반드시 멤버 함수) | X |
비교 연산자 operator==, operator!=, operator<, operator> |
O (왼쪽 피연산자가 자신과 동일할 때 가능) | O (왼쪽 피연산자가 다른 클래스일 때는 전역 함수 필수) |
왼쪽 피연산자 - 표준 라이브러리 클래스 operator<<, operator>> |
X (불가능) | O (전역 함수 필수) |
전역 함수로도 사용할 순 있지만 멤버함수로 사용할 수 있다면 멤버함수가 가독성 면에서도 나은듯 하다
'프로그래밍 > C++' 카테고리의 다른 글
가상 함수 테이블 / RTTI (1) | 2023.10.10 |
---|---|
static (0) | 2023.08.16 |
연산자 오버로딩 (1) | 2023.08.13 |
전방 선언 (0) | 2023.05.21 |
레퍼런스 (참조자) (0) | 2023.03.01 |