프로그래밍 언어/C++

C++ OPP 3대요소 : 캡슐화

순정법사 2023.09.14

A. OPP 캡슐화

1. 프렌드

a. 프렌드란?

C++에서는 객체의 private 멤버는 해당 객체의 public 멤버 함수를 통해서만 접근 가능함

하지만 경우에 따라서 해당 객체의 멤버 한수가 아니여도 private 멤버에 접근해야만 할 경우가 생김

💥 이럴때마다 매번 새로운 public 함수를 작성하는건 비효율적

 

따라서 friend라는 새로운 접근 제어 키워드를 제공

🌟 지정한 대상에 한해 해당 객체의 모든 멤버에 접근할 수 있는 권한을 부여해줌

 

➡ 전역함수, 클래스, 멤버 함수의 세 가지 형태로 사용가능

 

b. 프렌드 함수 선언

📘 원형

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

 

위 프렌드 함수는 클래스 선언부에 원형이 포함되지만, 클래스의 멤버 함수는 ❌

but 멤버함수와 같은 접근 권한을 가지게 됨

 

  • 함수의 원형에서만 사용해야 하며, 정의에서는 사용 ❌
  • 프랜드 선언은 클래스 선언부의 public, prvate, protected 영역 등 어디에나 위치 가능 (차이 없음)

 

c. 프렌드의 필요성

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

이유는 멤버 함수의 호출 형태에 있음

 

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

멤버함수가 아닌 함수를 사용하면 해당 private 멤버에 접근 불가

따라서 이때 사용하는 것이 바로 프렌드

 

Rect changed_rect = origin_rect * 2; //올바른 예제 Rect changed_rect = 3 * origin_rect; //오류

 

위의 두 구문이 정상적으로 동작하기 위해서는 다른 함수를 객체가 호출하지 않는 전역 함수로 작성해야 함

이 전역 함수가 private 멤버에 접근하기 위해선 friend 키워드를 추가해야 함

 

⏳ 예제

#include <iostream> using namespace std; class Rect { private: ​​​​double height_; ​​​​double width_; public: ​​​​// 생성자: Rect 객체를 초기화하기 위한 생성자 ​​​​Rect(double height, double width); ​​​​// 현재 사각형의 크기를 출력하는 함수 ​​​​void DisplaySize(); ​​​​// * 연산자 오버로딩: 사각형 객체에 상수를 곱하는 연산을 정의 ​​​​Rect operator*(double mul) const; ​​​​// * 연산자의 오른쪽 피연산자가 Rect 객체인 경우를 처리하는 프렌드 함수 ​​​​friend Rect operator*(double mul, const Rect& origin); }; int main(void) { ​​​​// Rect 클래스의 객체 생성 및 초기화 ​​​​Rect origin_rect(10, 20); ​​​​// Rect 객체에 상수를 곱한 결과를 저장 ​​​​Rect rect01 = origin_rect * 2; ​​​​// 상수를 Rect 객체에 곱한 결과를 저장 ​​​​Rect rect02 = 3 * origin_rect; ​​​​// Rect 객체의 크기 출력 ​​​​rect01.DisplaySize(); ​​​​rect02.DisplaySize(); ​​​​return 0; } // Rect 클래스의 생성자 정의 Rect::Rect(double height, double width) { ​​​​height_ = height; ​​​​width_ = width; } // Rect 객체의 크기를 출력하는 멤버 함수 정의 void Rect::DisplaySize() { ​​​​cout << "이 사각형의 높이는 " << this->height_ << "이고, 너비는 " << this->width_ << "입니다." << endl; } // * 연산자 오버로딩: Rect 객체에 상수를 곱한 결과를 반환 Rect Rect::operator*(double mul) const { ​​​​return Rect(height_ * mul, width_ * mul); } // * 연산자의 오른쪽 피연산자가 Rect 객체인 경우를 처리하는 프렌드 함수 정의 Rect operator*(double mul, const Rect& origin) { ​​​​// Rect 객체의 멤버함수를 호출하여 상수를 곱한 결과를 반환 ​​​​return origin * mul; }

✨ 실행결과

이 사각형의 높이는 20이고, 너비는 40입니다. 이 사각형의 높이는 30이고, 너비는 60입니다.

 

🧡 상수 멤버 함수

멤버 함수 원형의 맨 마지막에 const 키워드를 추가하면, 멤버 함수를 상수 멤버 함수로 정의할 수 있음
상수 멤버 함수란 자신이 호출하는 객체를 수정하지 않는 읽기 전용 함수를 의미

 

2. 다양한 프렌드

a. 프렌드 클래스

프렌드는 전역함수, 클래스, 멤버함수의 세가지 형태로 사용 가능

 

🌟 프랜드 클래스는 해당 클래스의 모든 멤버 함수가 특정 클래스의 프랜드인 클래스를 의미

 

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

 

📘 선언

friend class 클래스이름;

 

위에서 선언한 Rect 클래스의 선언에 아래와 같은 선언이 존재하면 

 

friend class Display;

 

위 클래스의 모든 멤버 함수는 Rect 클래스에 대한 프렌드 접근 권한을 부여받게 됨➡ 즉, display 클래스의 모든 멤버 함수는 Rect 클래스의 모든 멤버에 접근할 수 있음

 

⏳ 예제 : Display 클래스의 모든 멤버 함수가 Rect 클래스의 모든 멤버에 접근할 수 있도록 선언

#include <iostream> #include <cmath> using namespace std; class Rect { private: double height_; double width_; public: Rect(double height, double width); // 생성자 void height() const; void width() const; friend class Display; // 프렌드 클래스 선언 }; class Display { public: void ShowSize(const Rect& target); void ShowDiagonal(const Rect& target); }; int main(void) { Rect rect01(10, 20); ‌Display rect_display; ‌rect_display.ShowSize(rect01); ‌rect_display.ShowDiagonal(rect01); return 0; } Rect::Rect(double height, double width) { ‌height_ = height; ‌width_ = width; } void Rect::height() const { ‌cout << "이 사각형의 높이는 " << this->height_ << "입니다." << endl; } void Rect::width() const { ‌cout << "이 사각형의 너비는 " << this->width_ << "입니다." << endl; } void Display::ShowSize(const Rect& target) { ‌target.height(); ‌target.width(); } void Display::ShowDiagonal(const Rect& target) { double diagonal; ‌diagonal = sqrt(pow(target.height_, 2) + pow(target.width_, 2)); ‌cout << "이 사각형의 대각선의 길이는 " << diagonal << "입니다." << endl; }

✨ 실행결과

이 사각형의 높이는 10입니다. 이 사각형의 너비는 20입니다. 이 사각형의 대각선의 길이는 22.3607입니다.

 

b. 프렌드 멤버 함수

위 예제에서 ShowSize 함수는 Rect 클래스의 public 인터페이스만으로 구성,

ShowDiagonal 함수는 Rect 클래스의 private 멤버에 직접 접근하도록 구현

 

➡ 따라서 프렌드 설정이 필요한 함수는 ShowDiagonal 함수 뿐!

 

🌟 즉, 프렌드 멤버 함수란 해당 클래스의 특정 멤버 함수만을 프렌드로 지정하는 것을 의미

 

꼭 설정이 필요한 함수만 접근을 허락해, 정보 은닉 및 캡슐화 개념에 더욱 가깝게 구현 가능

 

⏳ 예제 : ShowDiagonal() 함수만이 Rect 클래스의 모든 멤버에 접근할 수 있도록 선언

#include <iostream> #include <cmath> using namespace std; class Rect; //Rect 클래스를 전방 선언 class Display { public: void ShowSize(const Rect& target); void ShowDiagonal(const Rect& target); }; 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); // 프렌드 멤버 함수 선언 }; int main(void) { Rect rect01(10, 20); ‌Display rect_display; ‌rect_display.ShowSize(rect01); ‌rect_display.ShowDiagonal(rect01); return 0; } Rect::Rect(double height, double width) { ‌height_ = height; ‌width_ = width; } void Rect::height() const { ‌cout << "이 사각형의 높이는 " << this->height_ << "입니다." << endl; } void Rect::width() const { ‌cout << "이 사각형의 너비는 " << this->width_ << "입니다." << endl; } void Display::ShowSize(const Rect& target) { ‌target.height(); ‌target.width(); } void Display::ShowDiagonal(const Rect& target) { double diagonal; ‌diagonal = sqrt(pow(target.height_, 2) + pow(target.width_, 2)); ‌cout << "이 사각형의 대각선의 길이는 " << diagonal << "입니다." << endl; }

✨ 실행결과

이 사각형의 높이는 10입니다. 이 사각형의 너비는 20입니다. 이 사각형의 대각선의 길이는 22.3607입니다.

 

c. 전방 선언 (forward declaration)

위 예제에서 Rect와 Display 클래스는 서로를 참조하고 있음

이런 쌍방참조를 순환참조(circular reference) 라고 함

이러한 순환 참조를 피하기 위해선 한 클래스를 다른 클래스의 앞에 미리 선언하는 전방선언을 사용해야 함

 

📘 선언

class 클래스이름;

 

프렌드 멤버 함수를 선언할 때에는 각 클래스의 선언 위치도 신경써야 함

선언된 함수를 처리하기 전에 먼저 선언을 알고있어야만 제대로 돌아가기 때문!

 

class Rect; // 전방 선언 class Display {...}; // Display 클래스 선언 class Rect {...}; // Rect 클래스 선언 ---> ok --------------------------------------------------- class Display; // 전방 선언 class Rect {...}; // Rect 클래스 선언 class Display {...}; // Display 클래스 선언 ---> 오류

 

 

3. 정적 멤버와 상수 멤버

a. 정적 멤버 변수(static member variable)

🌟 정적 멤버 : 클래스에는 속하지만, 객체 별로 할당되지 않고 클래스의 모든 객체가 공유하는 멤버

 

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

 

  • static 키워드를 사용하여 선언
  • 클래스 영역에서 선언되지만, 정의는 파일 영역에서 수행
  • 외부 연결(external linkage)을 가지므로, 여러 파일에서 접근
  • 클래스 멤버의 접근 제한 규칙이 적용되므로, 클래스의 멤버 함수나 프렌드만이 접근
  • 외부에서도 접근할 수 있게 하고 싶으면, 정적 멤버 변수를 public 영역에 선언

 

⏳ person_count_라는 정적 멤버 변수를 선언

#include <iostream> using namespace std; 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; // 정적 멤버 변수의 정의 및 초기화 int main(void) { Person hong("길동", 29); ‌hong.ShowPersonInfo(); Person lee("순신", 35); ‌lee.ShowPersonInfo(); return 0; } Person::Person(const string& name, int age) { ‌name_ = name; ‌age_ = age; ‌cout << ++person_count_ << " 번째 사람이 생성되었습니다." << endl; } void Person::ShowPersonInfo() { ‌cout << "이 사람의 이름은 " << name_ << "이고, 나이는 " << age_ << "살입니다." << endl; }

✨ 실행결과

1 번째 사람이 생성되었습니다. 이 사람의 이름은 길동이고, 나이는 29살입니다. 2 번째 사람이 생성되었습니다. 이 사람의 이름은 순신이고, 나이는 35살입니다.

 

b. 정적 멤버 함수(static member function)

해당 클래스의 객체를 생성하지 않고도, 클래스 이름만으로 호출

 

  • static 키워드를 사용하여 선언
  • 객체를 생성하지 않고 클래스 이름만으로 호출
  • 객체를 생성하지 않으므로, this 포인터 ❌
  • 특정 객체와 결합하지 않으므로, 정적 멤버 변수밖에 사용할수 ❌

 

📘 문법

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

 

⏳ 예제 : 정적 멤버 함수 person_count()를 선언

#include <iostream> using namespace std; 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 main(void) { Person hong("길동", 29); Person lee("순신", 35); ‌cout << "현재까지 생성된 총 인원 수는 " << Person::person_count() << "명입니다." << endl; return 0; } Person::Person(const string& name, int age) { ‌name_ = name; ‌age_ = age; ‌cout << ++person_count_ << " 번째 사람이 생성되었습니다." << endl; } void Person::ShowPersonInfo() { ‌cout << "이 사람의 이름은 " << name_ << "이고, 나이는 " << age_ << "살입니다." << endl; } int Person::person_count() // 정적 멤버 함수의 정의 { return person_count_; }

✨ 실행결과

1 번째 사람이 생성되었습니다. 2 번째 사람이 생성되었습니다. 현재까지 생성된 총 인원 수는 2명입니다.

 

c. 상수 멤버 변수(constant member variable)

🌟 한 번 초기화하면, 그 값을 변경할 수 없는 멤버 변수를 의미

 

상수 멤버 변수는 변수의 타입 앞에 const 키워드를 사용하여 선언

클래스 전체에 걸쳐 사용되는 중요한 상수는 상수 멤버 변수로 정의하여 사용

 

📘 문법

const 타입 멤버변수이름;

 

d. 상수 멤버 함수(constant member function)

🌟 호출한 객체의 데이터를 변경할 수 없는 멤버 함수

 

상수 멤버 함수는 함수의 원형 마지막에 const 키워드를 사용하여 선언

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

 

📘 문법

함수의원형 const;

 

 

 


출처 : http://www.tcpschool.com/cpp/cpp_encapsulation_friend

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com