본문 바로가기
C++

C++ OOP캡슐화

by godfeeling 2020. 7. 5.

프렌드(friend)

C++에서 객체의 private 멤버에는 해당 객체의 public 멤버 함수를 통해서만 접근할 수 있다고 했습니다.

 

하지만 경우에 따라서는 해당 객체의 멤버 함수가 아닌 함수도 private 멤버에 접근해야만 할 경우가 발생합니다.

 

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

 

 

 

따라서 C++에서는 이러한 경우를 위해 프렌드(friend)라는 새로운 접근 제어 키워드를 제공합니다.

 

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

 

이러한 friend 키워드는 전역 함수, 클래스, 멤버 함수의 세 가지 형태로 사용할 수 있습니다.

 

프렌드 함수 선언

C++에서 프렌드 함수는 friend 키워드를 사용하여 다음과 같이 선언합니다.

 

원형

friend 클래스이름 함수이름(매개변수목록);

 

 

 

이렇게 선언된 프렌드 함수는 클래스 선언부에 그 원형이 포함되지만, 클래스의 멤버 함수는 아닙니다.

 

이러한 프렌드 함수는 해당 클래스의 멤버 함수는 아니지만, 멤버 함수와 같은 접근 권한을 가지게 됩니다.

 

friend 키워드는 함수의 원형에서만 사용해야 하며, 함수의 정의에서는 사용하지 않습니다.

 

프렌드 선언은 클래스 선언부의 public, private, protected 영역 등 어디에나 위치할 수 있으며, 위치에 따른 차이는 전혀 없습니다.

 

프렌드의 필요성

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

 

그 이유는 바로 멤버 함수의 호출 형태에 있습니다.

 

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

 

 

 

하지만 멤버 함수가 아닌 함수를 사용하면 해당 객체의 private 멤버에 접근할 수 없게 됩니다.

 

따라서 이때 사용하는 것이 바로 프렌드(friend)입니다.

 

 

 

다음 예제는 사각형을 나타내는 Rect 클래스를 정의하면서, 크기를 조절하기 위해 곱셈 연산자(*)를 오버로딩하는 예제입니다.

 

예제

Rect Rect::operator*(double mul) const

 

{

 

return Rect(height_ * mul, width_ * mul);

 

}

 

 

위 예제의 operator*() 함수에서 주석 처리된 구문처럼 피연산자의 순서를 바꾸어 실행하면 오류가 발생할 것입니다.

 

그 이유는 멤버 함수란 왼쪽 피연산자인 객체가 호출하는 형태가 되어야 하기 때문입니다.

 

 

 

따라서 두 구문이 모두 정상적으로 동작하기 위해서는 지금의 operator*() 함수뿐만 아니라 매개변수의 순서가 다른 또 하나의 operator*() 함수를 객체가 호출하지 않는 전역 함수로 작성해야 합니다.

 

그 전역 함수가 private 멤버인 height_width_에 접근하기 위해서는 friend 키워드를 추가해야 합니다.

 

 

 

예제

class Rect

 

{

 

private:

 

double height_;

 

double width_;

 

public:

 

Rect(double height, double width); // 생성자

 

void DisplaySize();

 

Rect operator*(double mul) const;

 

friend Rect operator*(double mul, const Rect& origin); // 프렌드 함수

 

};

 

...

 

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

 

Rect operator*(double mul, const Rect& origin) { return origin * mul; }

 

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

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

 

프렌드 클래스

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

 

 

 

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

 

프렌드 클래스란 해당 클래스의 모든 멤버 함수가 특정 클래스의 프렌드인 클래스를 의미합니다.

 

 

 

C++에서 프렌드 클래스는 다음과 같이 선언합니다.

 

문법

friend class 클래스이름;

 

 

 

만약 Rect 클래스의 선언에 다음과 같은 프렌드 클래스의 선언이 존재한다고 가정합시다.

 

예제

friend class Display;

 

 

 

이때 Display 클래스의 모든 멤버 함수는 Rect 클래스에 대한 프렌드 접근 권한을 부여받게 됩니다.

 

, Display 클래스의 모든 멤버 함수는 Rect 클래스의 모든 멤버에 접근할 수 있습니다.

 

 

 

다음 예제는 Display 클래스의 모든 멤버 함수가 Rect 클래스의 모든 멤버에 접근할 수 있도록 선언한 예제입니다.

 

예제

class Rect

 

{

 

private:

 

double height_;

 

double width_;

 

public:

 

Rect(double height, double width); // 생성자

 

void height() const;

 

void width() const;

 

friend class Display; // 프렌드 클래스 선언

 

};

 

cmath 헤더 파일에 포함된 sqrt() 함수는 제곱근을, pow(a,b)ab(ab) 값을 구해주는 함수입니다.

프렌드 멤버 함수

프렌드 클래스란 해당 클래스의 모든 멤버 함수가 특정 클래스의 프렌드가 됩니다.

 

하지만 멤버 함수에 따라 특정 클래스의 프렌드 설정이 필요 없는 멤버 함수도 있습니다.

 

 

 

앞선 예제에서 ShowDiagonal() 함수는 Rect 클래스의 private 멤버에 직접 접근하도록 구현되어 있습니다.

 

하지만 ShowSize() 함수는 Rect 클래스의 public 인터페이스만으로 구현되어 있습니다.

 

따라서 Display 클래스에서 Rect 클래스에 대해 프렌드 설정이 필요한 함수는 ShowDiagonal() 함수뿐입니다.

 

 

 

이처럼 프렌드 멤버 함수란 해당 클래스의 특정 멤버 함수만을 프렌드로 지정하는 것을 의미합니다.

 

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

 

 

 

다음 예제는 Display 클래스의 ShowDiagonal() 함수만이 Rect 클래스의 모든 멤버에 접근할 수 있도록 선언한 예제입니다.

 

예제

class Rect

 

{

 

private:

 

double height_;

 

double width_;

 

public:

 

Rect(double height, double width); // 생성자

 

void height() const;

 

void width() const;

 

friend void Display::ShowDiagonal(const Rect& target); // 프렌드 멤버 함수 선언

 

};

 

 

전방 선언(forward declaration)

앞선 예제에서 Rect 클래스는 Display 클래스를 참조하고, Display 클래스는 Rect 클래스를 참조하고 있습니다.

 

이처럼 두 클래스의 선언 내에서 서로를 참조하고 있는 상황을 순환 참조(circular reference)라고 합니다.

 

이러한 순환 참조를 피하기 위해서는 한 클래스를 다른 클래스의 앞에 미리 선언하는 전방 선언(forward declaration)을 사용해야 합니다.

 

 

 

C++에서 전방 선언은 다음과 같이 선언합니다.

 

문법

class 클래스이름;

 

 

 

위의 예제에서는 순환 참조를 피하고자 Rect 클래스를 전방 선언하였습니다.

 

 

 

또한, 프렌드 멤버 함수를 선언할 때에는 각 클래스의 선언 위치도 신경 써야 합니다.

 

앞선 예제에서는 Rect 클래스와 Display 클래스를 다음과 같은 순서로 선언하였습니다.

 

예제

class Rect; // 전방 선언

 

class Display {...}; // Display 클래스 선언

 

class Rect {...}; // Rect 클래스 선언

 

 

 

만약 다음과 같이 순서를 바꾸면 컴파일러는 오류를 발생시킬 것입니다.

 

예제

class Display; // 전방 선언

 

class Rect {...}; // Rect 클래스 선언

 

class Display {...}; // Display 클래스 선언

 

 

 

Rect 클래스 내에서 ShowDiagonal() 함수가 프렌드로 선언된 것을 처리하기 전에, 컴파일러는 이미 ShowDiagonal() 함수의 선언을 알고 있어야만 합니다.

 

따라서 순서를 바꾸게 되면 컴파일러는 프렌드로 선언된 ShowDiagonal() 함수를 알지 못하므로, 오류를 발생시킵니다.

 

 

정적 멤버 변수(static member variable)

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

 

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

 

 

 

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

 

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

 

 

 

정적 멤버 변수에도 클래스 멤버의 접근 제한 규칙이 적용되므로, 클래스의 멤버 함수나 프렌드만이 접근할 수 있습니다.

 

하지만 정적 멤버 변수를 외부에서도 접근할 수 있게 하고 싶으면, 정적 멤버 변수를 public 영역에 선언하면 됩니다.

 

 

 

다음 예제는 모든 Person 객체가 같이 공유하는 person_count_라는 정적 멤버 변수를 선언하는 예제입니다.

 

예제

class Person

 

{

 

private:

 

string name_;

 

int age_;

 

public:

 

static int person_count_; // 정적 멤버 변수의 선언

 

Person(const string& name, int age); // 생성자

 

~Person() { person_count_--; } // 소멸자

 

void ShowPersonInfo();

 

};

 

...

 

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

 

 

이 정적 멤버는 Person 객체가 생성될 때마다 1씩 증가하여, 현재까지 총 몇 개의 Person 객체가 생성되었는지를 알려줍니다.

 

정적 멤버 함수(static member function)

C++에서는 클래스의 멤버 함수도 정적(static)으로 선언할 수 있습니다.

 

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

 

문법

1. 객체이름.멤버함수이름(); // 일반 멤버 함수의 호출

 

2. 클래스이름.멤버함수이름(); // 정적 멤버 함수의 호출

 

 

 

정적 멤버 함수는 정적 멤버 변수를 선언하는 방법과 같이 static 키워드를 사용하여 선언합니다.

 

 

 

이러한 정적 멤버 함수는 다음과 같은 특징을 갖습니다.

 

 

 

1. 객체를 생성하지 않고 클래스 이름만으로 호출할 수 있습니다.

 

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

 

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

 

 

 

예제

class Person

 

{

 

private:

 

string name_;

 

int age_;

 

public:

 

static int person_count_; // 정적 멤버 변수의 선언

 

static int person_count(); // 정적 멤버 함수의 선언

 

Person(const string& name, int age); // 생성자

 

~Person() { person_count_--; } // 소멸자

 

void ShowPersonInfo();

 

};

 

...

 

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

 

...

 

int Person::person_count() // 정적 멤버 함수의 정의

 

{

 

return person_count_;

 

}

 

 

위의 예제에서는 정적 멤버 변수인 person_count_의 값을 출력하기 위해서 정적 멤버 함수 person_count()를 선언합니다.

 

상수 멤버 변수(constant member variable)

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

 

이러한 상수 멤버 변수는 변수의 타입 앞에 const 키워드를 사용하여 선언합니다.

 

문법

const 타입 멤버변수이름;

 

 

 

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

 

상수 멤버 함수(constant member function)

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

 

이러한 상수 멤버 함수는 함수의 원형 마지막에 const 키워드를 사용하여 선언합니다.

 

문법

함수의원형 const;

 

 

 

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

 

 

'C++' 카테고리의 다른 글

C++ OOP다형성  (0) 2020.07.05
C++ OOP상속성  (0) 2020.07.05
C++ 연산자 오버로딩  (0) 2020.07.05
C++ 생성자 & 소멸자  (0) 2020.07.05
C++ 클래스  (0) 2020.07.05

댓글