-
[C++] 객체 간 관계C++/이것이 C++이다 2021. 10. 19. 00:09
C++는 OOP Language 입니다.
OOP적 관점에서 C++의 객체는 다른 객체와 특정한 관계가 성립합니다.
이번 글에서는 이하의 내용을 다뤄 볼 것입니다.
- 객체가 다른 객체와 어떤 관계가 될 수 있는지
- 객체 간 관계 형성과 관련된 문법이 무엇인지
기초와 관련된 만큼, 많은 내용이 포함되어 있지는 않습니다.
다만, 설계와 관련된 내용이 조금 나오는 관계로 짚고 넘어가면 좋을 것 같습니다.
1. friend 키워드
더보기friend 키워드는 클래스 내부에서, 함수나 클래스 선언 앞에 사용되는 키워드 입니다.
다음과 같이 사용합니다.
friend class [Class Name]; friend [Function Prototype];
이렇게 선언된 클래스와 함수는 접근 제어 지시자의 영향을 받지 않습니다.
키워드의 사전적 의미 그대로, 선언한 클래스와 함수를 친구처럼 대우하는 것 입니다.
예제를 살펴보겠습니다.
#include <iostream> using namespace std; class MyClass { public: MyClass(int param) : my_data(param) { } int get_data() { return my_data; } friend void function(MyClass&); // Line 9 private: int my_data; }; void function(MyClass& data) { data.my_data += 10; // Line 16 } int main() { MyClass a(10); cout << a.get_data() << "\n"; function(a); cout << a.get_data() << "\n"; return 0; }
Line 9에서 function 함수를 friend로 선언했고, 이후 전역함수 function에서 MyClass의 private 멤버 변수에 접근한 것을 볼 수 있습니다.
이와 같이, friend로 클래스, 함수는 접근 제어 지시자의 영향을 받지 않습니다.
그렇다면 이런 의문이 들 수 있습니다.
정보은닉, 캡슐화 등 여러 이유로 접근을 차단했는데, 너무 쉽게 풀어주는것이 아닌가?
지난 글(다중 상속) 에서 언급된 내용과 어느정도 궤를 같이 하는 질문이라고 생각합니다.
C++은 개발자의 자유도를 최대한 보장해주는 언어입니다.
상황에 따라서는 설정한 접근 제어 지시자를 풀어야 하는 상황이 발생할 수 있는 것 입니다.
또 다른 이유로는, 객체 간 응집성을 보장해주기 위함입니다.
응집성을 보장해준다는 것은, 서로 다른 객체라도 관련이 깊다면 긴밀하게 엮일 수 있도록 묶어주는 것 입니다.
2. 응집성
더보기위 문단에 언급된 응집성에 대해 이야기를 이어나가보겠습니다.
응집성은 객체 간의 관계를 긴밀하게 묶어주는 것 입니다. 하나의 클래스로 묶어두기에는 애매한 관계지만, 서로 관련이 없다고 하기에는 애매한 객체들을 friend 키워드로 묶어줄 수 있습니다.
예제를 살펴보겠습니다.
#include <iostream> using namespace std; class MyNode { friend class MyList; private: MyNode(const int param) : data(param) {} int data; MyNode* next_node = nullptr; }; class MyList { public: MyList() : head_node(new MyNode(0)){} void add_node(const int data) { MyNode* node = new MyNode(data); MyNode* temp_node = head_node; while (temp_node->next_node != nullptr) { temp_node = temp_node->next_node; } temp_node->next_node = node; } ~MyList() { MyNode* node = head_node->next_node; MyNode* d_node = nullptr; while (node) { d_node = node; node = node->next_node; cout << d_node->data << "\n"; delete d_node; } head_node->next_node = nullptr; } private: MyNode* head_node; }; int main() { MyList list; list.add_node(1); list.add_node(2); list.add_node(3); return 0; }
위 예제는 간단하게 삽입 연산만 구현된 단일 연결 리스트 입니다.
주목해야 할 점은 MyNode의 구성입니다. friend로 MyList를 선언했고, 생성자를 private로 설정했습니다.
이런 구성을 통해 MyNode의 생성은 MyList 내부에서만 할 수 있게 되었습니다.
두 클래스는 노드와 리스트라는 서로 다른 객체이지만, friend 키워드를 통해 한 객체에 준하는 관계가 되었습니다.
3. 객체 간 집합 관계
더보기지금까지 살펴본 객체 간 관계의 대표적인 예시는 상속 관계 입니다.
이번 문단에서는 위 문단에서 살펴본 응집성과 관련된, 객체 간 집합 관계에 대해 살펴보겠습니다.
집합 관계에도 두 종류가 있습니다.
첫 번째로는 위 문단의 연결 리스트와 같은 Composition 관계입니다. MyList를 구성하는 MyNode가 존재하듯이, 큰 객체 아래에 하위 객체가 존재하는 관계입니다.
예제를 살펴보겠습니다.
#include <iostream> using namespace std; class MyNode { friend class MyList; private: MyNode(const int param) : data(param) {} int data; MyNode* next_node = nullptr; }; class MyList { public: MyList() : head_node(new MyNode(0)){} void add_node(const int data) { MyNode* node = new MyNode(data); MyNode* temp_node = head_node; while (temp_node->next_node != nullptr) { temp_node = temp_node->next_node; } temp_node->next_node = node; } void print() { MyNode* node = head_node->next_node; while (node) { cout << node->data << "\n"; node = node->next_node; } } ~MyList() { MyNode* node = head_node->next_node; MyNode* d_node = nullptr; while (node) { d_node = node; node = node->next_node; cout << d_node->data << "\n"; delete d_node; } head_node->next_node = nullptr; } private: MyNode* head_node; }; class MyUI { public: int print_menu() { cout << "[1] Add [2] Print [0] Exit \nInput : "; cout.flush(); int input = -1; cin >> input; return input; } void run() { int data; int input; while ((input = print_menu()) != 0) { switch (input) { case 1: cout << "Data : "; cout.flush(); cin >> data; list.add_node(data); continue; case 2: list.print(); continue; case 0: break; default: continue; } } } private: MyList list; }; int main() { MyUI ui; ui.run(); return 0; }
[1] Add [2] Print [0] Exit Input : 1 Data : 123 [1] Add [2] Print [0] Exit Input : 1 Data : 425 [1] Add [2] Print [0] Exit Input : 2 123 425 [1] Add [2] Print [0] Exit Input : 0 123 425
위 문단의 연결 리스트에서 몇 가지가 추가되었습니다.
MyList는 print함수가, 그 외에 사용자의 입력을 받을 수 있는 UI 클래스가 추가되었습니다.
주목해야 할 점은, MyUI클래스가 MyList클래스의 인스턴스를 멤버로 가지고 있는 점 입니다.
따라서, MyUI의 소멸시점에 MyList도 함께 소멸합니다.
위와 같이 여러 객체 사이에서 주도적인 객체가 존재하고, 그 객체의 일부를 이루는 다른 객체들이 존재하는 것이 Composition 관계 입니다.
다음은 Aggregation관계 입니다.
Aggregation관계는 독립적인 여러 객체가 모여 하나를 이루는 것 입니다.
예제를 살펴보겠습니다.
#include <iostream> using namespace std; class MyNode { friend class MyList; private: MyNode(const int param) : data(param) {} int data; MyNode* next_node = nullptr; }; class MyList { public: MyList() : head_node(new MyNode(0)){} void add_node(const int data) { MyNode* node = new MyNode(data); MyNode* temp_node = head_node; while (temp_node->next_node != nullptr) { temp_node = temp_node->next_node; } temp_node->next_node = node; } void print() { MyNode* node = head_node->next_node; while (node) { cout << node->data << "\n"; node = node->next_node; } } ~MyList() { MyNode* node = head_node->next_node; MyNode* d_node = nullptr; while (node) { d_node = node; node = node->next_node; cout << d_node->data << "\n"; delete d_node; } head_node->next_node = nullptr; } private: MyNode* head_node; }; class MyUI { public: MyUI(MyList& r_list) : list(r_list){} int print_menu() { cout << "[1] Add [2] Print [0] Exit \nInput : "; cout.flush(); int input = -1; cin >> input; return input; } void run() { int data; int input; while ((input = print_menu()) != 0) { switch (input) { case 1: cout << "Data : "; cout.flush(); cin >> data; list.add_node(data); continue; case 2: list.print(); continue; case 0: break; default: continue; } } } private: MyList& list; }; int main() { MyList list; MyUI ui(list); ui.run(); ui.~MyUI(); cout << "------------\n"; MyUI ui_two(list); ui_two.run(); return 0; }
[1] Add [2] Print [0] Exit Input : 1 Data : 123 [1] Add [2] Print [0] Exit Input : 1 Data : 321 [1] Add [2] Print [0] Exit Input : 2 123 321 [1] Add [2] Print [0] Exit Input : 0 ------------ [1] Add [2] Print [0] Exit Input : 2 123 321 [1] Add [2] Print [0] Exit Input : 0 123 321
Composition 예제에서 일부 코드가 수정되었습니다.
MyUI의 멤버 변수 MyList가 참조 형식이 되었고, 그에 따라 생성자에서 초기화를 할 수 있도록 변경했습니다.
또한 main함수에서 차이점을 알 수 있도록, UI를 두개 생성했습니다.
Aggregation 관계에서는 첫 번째 MyUI가 소멸되어도 List는 그대로 남아, 두 번째 MyUI에서 정보가 그대로 남아있는 것을 볼 수 있습니다.
이와 같이 서로 모여서 작동하지만, 따로 떨어질 수 있는 관계가 Aggregation 관계 입니다.
두 관계를 설명할 때 거의 동일한 객체들을 사용한 이유는 객체의 성질에 따라 Composition 관계인지, Aggregation 관계인지 정해지지 않는다는 이야기를 하기 위함이었습니다.
위 예제는 자료구조와, 사용자 인터페이스와 관련된 객체입니다.
위와 같은 구조에서는 Aggregation 관계가 되는 것이 좋은 설계가 될 수 있습니다.
이와 관련된 내용은 차후 디자인 패턴과 관련된 글에서 다룰 수 있도록 하겠습니다.
클래스 설계와, 설계된 클래스 간의 올바른 관계 형성은 개발 과정이나, 이후 유지보수 단계에서 많은 도움이 됩니다.
이번 글에서 다룬 C++의 문법은 friend 키워드 하나뿐이지만, 설계에 관련된 내용이 포함되어 있는 만큼 중요도는 낮지 않다고 생각됩니다.
다음 글은 C++의 생산성과 관련된 내용, 템플릿입니다.
읽어주셔서 감사합니다.
'C++ > 이것이 C++이다' 카테고리의 다른 글
[C++] 스마트 포인터 (0) 2021.11.03 [C++] 템플릿 (0) 2021.10.26 [C++] 다중 상속 (0) 2021.09.24 [C++] 형 변환 연산자 (0) 2021.09.19 [C++] 가상 함수 (0) 2021.09.05