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

+ Recent posts