C++ 프렌드(friend)

프로그래밍/C++ 2019.10.15 12:39

데이터 은닉을 통하여 멤버 함수를 통해서만 private 데이터 멤버에 접근이 가능했습니다.

하지만 이 제약이 엄격하여 특정 프로그래밍 문제를 해결하지 못하는 경우가 있습니다.

그래서 나온 게 프렌드라는 겁니다.

프렌드는 세 가지 형태로 사용됩니다.

 

  • 프렌드 함수

  • 프렌드 클래스

  • 프렌드 멤버 함수

 

프렌드가 사용되어야 할 때

클래스 멤버 함수는 아니지만 해당 클래스의 데이터 멤버에 접근해야 할 때 필요합니다.

기본적으로 데이터 은닉에 의하여 외부에서 데이터 멤버는 접근할 수 없게 됩니다.

하지만 프렌드는 클래스 멤버 함수와 동일한 접근권한을 가지게 됩니다.

(말 그대로 프렌드는 친구라는 뜻입니다.)

지나가는 사람보고 개인정보 좀 알려달라고 할 수 없으니, 친구를 맺어 개인정보를 알려달라고(접근) 할 수 있습니다.

 

프렌드가 필요한 상황

#include <iostream>
class Person {
private:
       int age;
public:
       
       Person(int p_age) {
              age = p_age;
       }
       Person operator+(int a) {
              Person temp(age + a);
              return temp;
       }
       void Show() const {
              std::cout << age << "\n";
       }
};
int main(void) {
       Person per = Person(11);
       per.Show();
       per = per + 1;
       per.Show();
       per = 1 + per;
       per.Show();
       return 0;
}

연산자 오버 로딩에서도 확인했듯이 제약 6번, "왼쪽의 피연산자는 반드시 사용자 정의 데이터형이어야 합니다." 때문에 

per = 1 + per;

는 컴파일 에러가 발생합니다.

연산 측면에서 보면 per + 1이나 1 + per이나 같은 의미인데 말입니다. 

이걸 해결하려면 클래스 멤버 함수에서 연산자 오버 로딩은 그대로 놔두고, 해당 연산을 지원하도록 새로운 연산자 오버로딩 함수(전역)를 사용해야 합니다.

하지만 클래스의 멤버 데이터는 데이터 은닉 때문에 클래스 멤버 함수가 아니면 접근이 불가능한 상태입니다.

#include <iostream>
class Person {
private:
       int age;
public:
       
       Person(int p_age) {
              age = p_age;
       }
       Person operator+(int a) {
              Person temp(age + a);
              return temp;
       }
       void Show() const {
              std::cout << age << "\n";
       }
};
Person operator+(int a, Person b) {
       Person temp(b.age + a);
       return temp;
}
int main(void) {
       Person per = Person(11);
       per.Show();
       per = per + 1;
       per.Show();
       per = 1 + per;
       per.Show();
       return 0;
}

그래서 연산자 오버 로딩 함수를 정의하여 연산(함수 호출)에는 문제가 없지만, 멤버 변수인 age에 접근이 불가능하므로 컴파일 에러가 발생하게 됩니다.

 

friend 사용하여 해결하기

하지만 friend를 함으로써 해당 클래스 멤버 함수의 접근권한(데이터 멤버에 접근할 수 있음)과 동일한 권한을 얻을 수 있게 되고 접근이 가능해지니 데이터 멤버의 수정 또한 가능합니다.

#include <iostream>
class Person {
private:
       int age;
       friend Person operator+(int, Person); //friend
public:
       Person(int p_age) {
              age = p_age;
       }
       Person operator+(int a) {
              Person temp(age + a);
              return temp;
       }
       void Show() const {
              std::cout << age << "\n";
       }
};
Person operator+(int a, Person b) {
       Person temp(b.age + a);
       return temp;
}
int main(void) {
       Person per = Person(11);
       per.Show();
       per = per + 1;
       per.Show();
       per = 1 + per;
       per.Show();
       return 0;
}

클래스 데이터 멤버에 접근하려고 하는 클래스에 연산자 오버 로딩된 함수의 선언을 써주고 맨 앞에 friend 키워드를 써주면 됩니다.

프렌드를 사용하려면 3가지만 알면 됩니다.

  • 함수(operator+)는 클래스 선언 안에 되지만 멤버 함수가 아닙니다. 그러므로 멤버 연산자를 사용하여 호출되지 않습니다.

  • 함수는 멤버 함수가 아니지만 멤버 함수와 동일한 접근권한을 가지고 있습니다.

  • 함수를 정의할 때 ::연산자를 사용하지 않고, friend키워드 또한 사용하지 않습니다.

  • friend 선언을 할 때에는 접근제어자의 영향은 받지 않습니다.

멤버 함수와 함수에서의 연산자 오버 로딩

멤버 함수에서의 연산자 오버 로딩이나 함수에서의 연산자 오버로딩이나 내부적으로 같은 코드를 짠다면 결과는 같겠지만 사용(호출) 과정에서 매개변수 부분이 틀립니다.

일단 멤버 함수에서의 연산자 오버 로딩은 this포인터를 통하여 암시적으로 하나가 전달되고, 함수에서의 연산자 오버로딩은 명시적으로 2개의 매개변수가 필요합니다.

멤버 함수에서는 암시적으로 하나만 전달되기 때문에, 필요한 매개변수는 1개뿐입니다.

 

그럼 무엇을 써야 하는가?

멤버 함수와 함수에서의 연산자 오버 로딩을 2개 다 정의한다면 컴파일러는 1개를 선택해야 합니다.

컴파일러는 의도를 모르니 모호하다고 에러가 발생하게 될 겁니다.

일부 연산자들에 대해서는 멤버 함수가 유일한 방법입니다.

또는 클래스 설계에 따라서 클래스에 대한 데이터형 변환을 정의했을 경우 함수가 더 유리할 수도 있습니다.

 

댓글을 달아 주세요