본문 바로가기
lang/C,C++

[Class와 OOP] 4. OOP 캡슐화, 프렌드(freind), static, const 멤버 변수, 함수

by Wordbe 2019. 10. 9.
728x90

다시 복습하면, OOP의 중요한 4개의 개념으로
추상화, 캡슐화, 상속성, 다형성이 있었습니다.
추상화는, 여러 객체들의 특징(속성과 기능)을 하나의 부류로 표현하는 개념이므로, class를 통해 이를 구현하고,
사용하는 방법을 배워보았습니다.

이번에는 캡슐화를 다뤄보겠습니다.

1. 프렌드(friend)

경우에 따라서는 해당 객체의 멤버 함수가 아님에도 private 멤버에 접근해야만 하는 경우가 있습니다.

연산자 오버로딩 처럼요.

이럴 때마다 private 멤버에 접근하기 위한 public 멤버함수를 새로 작성하는 것은 비효율적입니다.

프렌드는 지정한 대상에 한해 해당 객체의 모든 멤버에 접근할 수 있는 권한을 부여해 줍니다.

  • friend 키워드는 함수의 원형에서만 사용해야 하고, 함수의 정의에서는 사용하지 않습니다.
  • 프렌드 선언은 클래스 선언부의 public, private, protected 영역 어디에나 위치할 수 있고, 차이는 없습니다.

프렌드의 필요성

C++에서 클래스에 대해 이항 연산자를 오버로딩할 때 프렌드가 자주 필요합니다.

멤버 함수는 왼쪽 피연산자인 객체가 호출하는 형태이므로, 이항 연산자의 매개변수 순서 또는 타입에 민감해집니다.

하지만, 멤버함수가 아닌 함수를 사용하면 해당 객체의 private에 접근할 수 없고, 이 때 프렌드를 사용합니다.

예를 들어봅시다.

class Rect{
private:
    int height_;
    int width_;
public:
    Rect(int height=10, int width=20) : height_(height), width_(width) {};
    Rect operator* (double mul) const;
    int get_height() { return height_; }
    int get_width() { return width_; }
};

Rect Rect::operator*(double mul) const{
    return Rect(height_ * mul, width_ * mul);
}

Rect a;
Rect res = a*2;
Rect res2 = 3*a;
cout << res.get_height() << " " << res.get_width() << endl;
cout << res2.get_height() << " " << res2.get_width() << endl;

이항 연산자는 순서가 있기 때문에, operator*() 매개변수에 있는 값은

우변, rhs(right hand side) 에 적용되게 됩니다.

따라서 3 * a와 같은 연산은 할 수 없어 이부분을 재정의 해주어야 합니다.

1) operator*(rhs)

2) operator*(lhs, rhs)

둘 중 한가지 방식으로 연산자 오버로딩을 할 수 있으므로,

3 * a와 같은 연산을 하기 위해서는 2번방법을 사용해야 합니다.

Rect operator*(double lhs, const Rect& rhs) {
    return Rect(lhs * rhs.height_, lhs * rhs.width_);
}

하지만 여기서 rhs는 private에 접근 할 수 없기 때문에,

이 때 프렌드를 통해 이 외부함수도 private에 접근할 수 있게 해주어야 합니다.

friend Rect operator* (double lhs, const Rect& rhs);

위 문장을 Rect 클래스 private, public, protected 아무곳이나 넣어주면 되겠습니다.

참고로 멤버 함수 원형의 맨 마지막에 const 키워드를 추가해주면, 멤버함수를 상수 멤버함수로 정의할 수 있습니다.

상수 멤버 함수란, 자신이 호출하는 객체를 수정하지 않는 읽기 전용함수를 의미합니다.

2. 다양한 프렌드

프렌드는 전역함수, 클래스, 멤버함수 세 가지 형태로 사용할 수 있습니다.

전역함수는 위에서 말씀드렸으므로 넘기겠습니다.


프렌드 클래스

만약 두 클래스가 기능상 서로 밀접한 관계가 있고, 상대방의 private멤버에 접근해야 한다면 클래스 자체를 프렌드로 선언하는 것이 좋습니다.

Display 클래스를 프렌드 클래스로 선언하기 위해서는

friend class Display;

를 Rect 클래스 안에 접근 제어 지시자 안에 넣어주시면 됩니다.

예를 들면 public입니다.


프렌드 멤버함수

관련된 클래스에서 특정한 멤버함수만 프렌드가 되게하는 기능입니다.

이것은 프렌드 설정이 꼭 필요한 함수에 대해서만 접근을 허락하므로, 정보은닉(data hiding) 및 캡슐화(encapsulation) 개념에 가깝게 구현할수 있게 됩니다.

friend void Display::ShowDiagonal(const Rect& target);

프렌드 클래스와 마찬가지로, Rect 클래스 안에 넣어주시면 됩니다.

주의할 점으로는 클래스를 선언하는 순서를 잘 지켜주어야 컴파일 에러가 나지 않는다는 점입니다.

class Rect; // 전방 선언(forward declaration)
class Display {...};
class Rect {...};

3. 정적 멤버와 상수 멤버

정적 멤버 변수 (static member variable)

정적 멤버란 클래스에 속하지만, 객체 별로 할당되지 않고, 클래스의 모든 객체가 공유하는 멤버를 의미합니다.

멤버 변수가 정적(static)으로 선언되면, 해당 클래스의 모든 객체에 대해 하나의 데이터만이 유지 됩니다.

정적 멤버 변수는 클래스 영역에서 선언되지만, 정의는 파일 영역에서 수행됩니다.

정적 멤버 변수는 외부연결(external linkage)을 가지므로, 여러 파일에서 접근할 수 있습니다.

그러면서도 private에 있으면 멤버함수나 프렌드만이 접근할 수 있습니다.

예시)

class Person{
private:
    string name_;
    int age_;
public:
    static int person_count_;            // 정적 멤버 변수의 선언
      static int perso_count();             // 정적 멤버 함수의 선언
    Person(const string& name, int age); // 생성자
    ~Person() { person_count_--; }       // 소멸자
    void ShowPersonInfo();
};  

int Person::person_count_ = 0; // 정적 멤버 변수의 정의 및 초기화

int Person::person_count(){
    return person_count_;
}

정적 멤버 함수(static member function)

정적으로 선언된 멤버 함수는 해당 클래스의 객체를 생성하지 않고도, 클래스 이름만으로 호출할 수 있습니다.

객체를 생성하지 않으므로, this 포인터를 가지지 않습니다.

특정 객체와 결합하지 않으므로, 정적 멤버 변수만 사용할 수 있습니다.

위 예제에서 person_count()가 이에 해당합니다.


상수 멤버 변수(constant member variable)

상수 멤버 변수란 한번 초기화 하면, 그 값을 변경할 수 없는 멤버 변수를 말합니다.

멤버 변수앞에 const를 붙여 선언하면 됩니다.

클래스 전체에 걸쳐 사용되는 중요한 상수는 상수 멤버 변수로 정의하여 사용하는 것이 좋습니다.


상수 멤버 함수(constant member function)

상수 멤버 함수란 호출한 객체 데이터를 변경할 수 없는 멤버 함수를 의미합니다.

함수의 원형 마지막에 const를 붙이고 선언하면 됩니다.

호출한 객체 데이터를 단순히 읽기만 하는 멤버 함수는 상수 멤버 함수로 정의하는 것이 정보 보호 측면에서 좋습니다.

[출처 - TOPSCHOOL.com http://tcpschool.com/cpp]

728x90

댓글