배움 저장소
[홍정모의 따라하며 배우는 C++] 10.객체들 사이의 관계에 대해 본문
10.1 객체들의 관계
Object Relationship
객체 지향 프로그램 설계 순서
(1) 프로그램이 수행하는 기능을 정의
(2) 기능을 수행하는데 필요한 여러 객체를 정의하고 상호작용을 결정
객체의 관계 다음과 같은 특성으로 구분할 수 있다
(1) 속한다: 객체 내부의 Instance를 다른 곳에서도 사용하는가?
(2) 관리: 해당 객체의 소멸을 관계있는 객체가 결정하는가?
(3) 방향성: 객체가 다른 객체의 멤버변수/함수를 활용하는기?
10.2 구성 관계
Composition
다음 예제에서 class Monster와 class Position2D는 Composition(구성관계)이다
main.cpp
#include "Monster.h"
using namespace std;
int main() {
// Goblin Move
Monster mob1("Goblin", 10, 15);
cout << mob1 << endl; // >> Goblin(10, 15)
mob1.MoveTo(5,5);
cout << mob1 << endl; // >> Goblin(5, 5)
// Slime Move
Position2D location(30,40);
Monster mob2("Slime", location);
cout << mob2 << endl; // >> Slime(30, 40)
mob2.MoveTo(Position2D(20,40));
cout << mob2 << endl; // >> Slime(20, 40)
}
Monster.h
#pragma once
#include <string>
#include "Position2D.h"
class Monster {
private:
std::string m_name; // class, char* + unsigned
//int m_x,m_y; // How about implement this to class?
Position2D m_location;
public:
Monster(const std::string &name, Position2D location):m_name(name), m_location(location) {}
Monster(const std::string &name, const int& x, const int& y) :m_name(name), m_location(Position2D(x, y)) {}
void MoveTo(const int& x, const int& y) { m_location.set(x,y); }
void MoveTo(Position2D location) { m_location.set(location); }
friend std::ostream& operator <<(std::ostream& out, const Monster& monster) {
out << monster.m_name << monster.m_location;
return out;
}
};
Position2D.h
#pragma once
#include <iostream>
class Position2D {
private:
int m_x, m_y;
public:
Position2D (const int& x, const int& y):m_x(x), m_y(y){};
void set(const int& x, const int& y) {
m_x = x;
m_y = y;
}
void set(const Position2D& location){
// m_x = location.m_x;
// m_y = location.m_y;
set(location.m_x, location.m_y); // easy to management
}
friend std::ostream& operator <<(std::ostream& out, const Position2D& position2d) {
out << "(" << position2d.m_x << "," << position2d.m_y << ")";
return out;
}
};
10.3 집합 관계
Aggregation
composition 관계에서 내부 객체의 정보를 다른 객체와 공유하지 않는다
- composition 관계를 구현할 때 인자 혹은 매개변수는 복사되어 사용된다. 아래 Instance "hermione"은 Instance "lect1"과 Instance "lect2"에서 사용된다. 이 때 Lecture 내부에서 Hermione Instance 정보를 어떻게 변경하든 원본은 변하지 않는다.
Student hermione("Hermione", 2);
Lecture lect1("Lecture One");
lect1.assignTeacher(Teacher("Prof. Snape"));
lect1.registerStudent(hermione);
Lecture lect2("Lecture Two");
lect2.assignTeacher(Teacher("Prof. Hong"));
lect2.registerStudent(hermione);
외부에서 가져온 객체를 사용할 때 원본값도 함께 바꾸어주려면 포인터를 활용해주면 된다. 이와같이 외부 객체와 상호작용하는 객체의 관계를 Aggregation(집합관계)라 한다. 다음은 두 객체의 집합관계를 표현한 예제이다.
이 때 참조는 사용할 수 없다. 참조는 선언과 동시에 참조할 대상이 정해져야 하기 때문이다.
main.cpp
#include "Lecture.h"
using namespace std;
int main() {
Teacher prof_snape("Prof. Snape");
Teacher prof_hong("Prof. Hong");
Student potter("Potter", 0);
Student ron("Ron", 1);
Student hermione("Hermione", 2);
Lecture lect1("Lecture One");
lect1.assignTeacher(&prof_snape);
lect1.registerStudent(&potter);
lect1.registerStudent(&ron);
lect1.registerStudent(&hermione);
Lecture lect2("Lecture Two");
lect2.assignTeacher(&prof_hong);
lect2.registerStudent(&potter);
{
cout << lect1 << endl;
cout << lect2 << endl;
lect1.IncreaseGrade();
lect2.IncreaseGrade();
cout << lect1 << endl;
cout << lect2 << endl;
}
}
위에서 Teacher와 Student는 지역변수이다. 값을 유지하려면 동적할당을 사용하자
Teacher *prof_snape = new Teacher("Prof. Snape");
Teacher *prof_hong = new Teacher("Prof. Hong");
Student *potter = new Student("Potter", 0);
Student *ron = new Student("Ron", 1);
Student *hermione = new Student("Hermione", 2);
// ...
delete everything above;
Lecture.h
아래 예제에서 포인터 대신 참조를 사용하려면 초기화 될 때 참조값이 결정되어야 한다.
#pragma once
#include "Student.h"
#include "Teacher.h"
#include <vector>
class Lecture{
private:
std::string m_name;
//Teacher teacher; // copied value
//std::vector<Student> students; // can't interact with outside
Teacher *teacher;
std::vector<Student*> students;
public:
Lecture(const std::string& name = "None_class"):m_name{name}, teacher{nullptr} {}
~Lecture() {}
//void assignTeacher(const Teacher& const teacher_input) { teacher = teacher_input; } copied val
//void registerStudent(const Student& const student) { students.push_back(student); } copied val
void assignTeacher(Teacher* const teacher_input) { teacher = teacher_input; }
void registerStudent(Student* const student) { students.push_back(student); }
void IncreaseGrade() {
std::cout << "After Study, Students get one more grade" << std::endl;
for (auto*& student : students)
{ student->setGrade(student->getGrade() + 1); }
//{ (*student).setGrade((*student).getGrade()+1); } // same as above
}
friend std::ostream& operator << (std::ostream& out, const Lecture& lecture) {
out << "Lecture name" << lecture.m_name << std::endl;
out << *(lecture.teacher) << std::endl;
for(unsigned int i=0; i<lecture.students.size(); ++i){
out << *(lecture.students[i]);
if(i != lecture.students.size()-1){ out << std::endl; }
}
return out;
}
};
Student.h
#pragma once
#include <string>
#include <iostream>
class Student{
private:
int m_grade;
std::string m_name;
public:
Student(const std::string& name="None_Student", const int& grade = 0) :m_name(name), m_grade(grade) {}
void setName(const std::string& name) { m_name = name; }
const std::string& getName() { return m_name; }
void setGrade(const int& grade) { m_grade = grade; }
const int& getGrade() { return m_grade; }
friend std::ostream& operator << (std::ostream& out, const Student& student) {
out << student.m_name << " " << student.m_grade;
return out;
}
};
Teacher.h
#pragma once
#include <string>
#include <iostream>
class Teacher {
private:
std::string m_name;
public:
Teacher(const std::string& name="None_Teacher") :m_name(name) {}
void setName(const std::string &name) { m_name = name; }
const std::string& getName(){ return m_name; }
friend std::ostream& operator << (std::ostream& out, const Teacher &teacher) {
out << teacher.m_name;
return out;
}
};
vector<type> VS vector<type*>
vector<type>을 선언한 이후 vector.push_back( value )에서 reference 매개변수를 value에 입력하였다. reference 매개변수를 입력하였으므로 저장된 값은 원본과 메모리를 공유할 것으로 예상했다. 예상은 틀렸다. 복사된 값이 저장되었다.어떤 값이 vector 내부에 저장되는지는 선언 때 선택한 자료형을 따른다.
vector<type>는 동적할당 메모리에 해당 값을 복사하여 넣는다
void Test(int& i) {
cout << & i << endl; // >> 008FFE4C
vector<int> v;
v.push_back(i);
cout << &v[0] << endl; // >> 00A64C38
}
int main() {
int x = 5;
cout << &x << endl; // >> 008FFE4C
Test(x);
}
vector<type*>는 동적할당 메모리에 해당 값(주소)를 넣는다
int x = -1;
const int const_x = 10;
cout << &x << " " << &const_x << endl; // >> 00D3FB3C 00D3FB30
vector<const int*> v_pointer;
v_pointer.push_back(&x);
v_pointer.push_back(&const_x);
cout << v_pointer[0] << " " << v_pointer[1] << endl;// >> 00D3FB3C 00D3FB30
분산처리(서버나 네트워크에서 사용)를 할 때는 저장공간이 분리되어 있어 데이터(student or teacher) 사본이 여러군데 존재해야 한다. 한 곳에서 특정 데이터를 업데이트하면 동기화 작업이 별도로 필요하다. 이를 고려하지 않고 집합관계로 구현하면 의도하지 않은 결과를 만날 수 있다
10.4 제휴 관계
Association
한 코드 파일 내에서 두 객체가 서로를 내부 변수로 사용하려면 한 객체는 전방선언을 해주어야 한다. 전방선언은 디버깅을 어렵게 만들지만 제휴관계일 때 피하기 어렵다. 만약 헤더-코드파일로 나눈다면 헤더 파일에서 서로를 전방선언을 해주어야 한다. 전방선언을 피하기 위하여 두 class를 관리해주는 새로운 class를 만들기도 한다
다음은 예제 코드이다
#include <string>
#include <iostream>
#include <vector>
using namespace std;
class Doctor; // forward declaration
class Patient {
private:
std::string m_name;
std::vector<Doctor*> m_doctors;
public:
friend class Doctor;
Patient(const std::string& name) :m_name(name) {}
void addDoctor(Doctor* const doctor) { m_doctors.push_back(doctor); }
void meetDoctor(); // below code makes error!
//void meetDoctor() { // Error! Can't figure out Doctor's member var
// for (auto& doctor : m_doctors)
// { cout << "meet Doctor :" << doctor->m_name << endl; }
//}
};
class Doctor {
private:
std::string m_name;
std::vector<Patient*> m_patients;
public:
friend class Patient;
Doctor(const std::string& name) :m_name(name) {}
void addPatient(Patient* const patient) { m_patients.push_back(patient); }
void meetPatient() { // OK, knowing patient's member var
for (auto*& patient : m_patients)
{ cout << m_name << " meet Patient :" << patient->m_name << endl; }
}
};
void Patient::meetDoctor() {
for (auto*& doctor : m_doctors)
{ cout << m_name << " meet Doctor :" << doctor->m_name << endl; }
}
int main() {
Patient* p1 = new Patient("Kim");
Patient* p2 = new Patient("Jason");
Patient* p3 = new Patient("Christina");
Doctor* d1 = new Doctor("Doctor Lee");
Doctor* d2 = new Doctor("Doctor Huberman");
p1->addDoctor(d1); d1->addPatient(p1);
p2->addDoctor(d2); d2->addPatient(p2);
p3->addDoctor(d1); d1->addPatient(p3);
p3->addDoctor(d2); d2->addPatient(p3);
p1->meetDoctor();
p2->meetDoctor();
p3->meetDoctor();
d1->meetPatient();
d2->meetPatient();
delete p1; delete p2; delete p3;
delete d1; delete d2;
}
Reflecive Association
- Instance가 자신과 동일한 자료형 Instance를 멤버변수로 가질 때이다.
10.5 의존 관계
Dependencies
객체 내부 멤버함수에서 필요한 특정 객체를 사용할 수 있다. 이 때 특정 객체를 부-객체, 멤버함수를 가지고 있는 객체를 주-객체라 부르자. 의존관계에서 부 객체는 주 객체의 멤버변수가 아니다. 부 객체는 임시로 주 객체 내부에서 생성되었다 사라진다. 따라서 주 객체의 선언에 부 객체는 나타나지 않는다.
main.cpp
#include "Worker.h"
int main() {
Worker worker;
worker.TimeCost();
}
Worker.h
#pragma once
class Worker {
public:
void TimeCost();
};
Worker.cpp
#include "Worker.h"
#include "Timer.h"
void Worker::TimeCost() {
Timer timer; // Start elapse
timer.elapse(); // End elapse
}
Time.h
#pragma once
#include <iostream>
#include <chrono> // the god of time
class Timer {
using clock_t = std::chrono::high_resolution_clock;
using second_t = std::chrono::duration<double, std::ratio<1>>;
std::chrono::time_point<clock_t> start_time = clock_t::now(); // capture current time for start
public:
void elapse() {
std::chrono::time_point<clock_t> end_time = clock_t::now(); // capture current time for end
std::cout << std::chrono::duration_cast<second_t>(end_time - start_time).count() << std::endl;
}
};
10.6 컨테이너 클래스
Container Classes
C++는 다양한 standard library container를 제공하고 있다. https://en.cppreference.com/w/cpp/container
직접 구현해보자
#include <iostream>
#include <initializer_list>
#include <cassert>
using namespace std;
class IntArray {
private:
int m_length;
int *m_data;
public:
IntArray(const initializer_list<int>& list){
initialize(list);
cout << "created! on " << &m_data << " val:" << m_data << endl;
}
~IntArray() {
if(m_data != nullptr) delete [] m_data;
cout << "deleted on " << &m_data << " val:" << m_data << endl;
m_data = nullptr;
}
void initialize(const initializer_list<int>& list){
m_length = list.size();
m_data = new int[m_length];
int i = 0;
for (auto& e : list) {
m_data[i] = e;
++i;
}
}
void reset() {
m_length = 0;
if(m_data == nullptr) return;
delete[] m_data;
m_data = nullptr;
}
void resize(int size) {
int *temp = new int[size];
m_length = (m_length < size)? m_length : size;
if (m_data != nullptr) {
for (int i = 0; i < m_length; ++i){
temp[i] = m_data[i];
}
}
for (int i = m_length; i < size; ++i){
temp[i] = 0;
}
if (m_data != nullptr) delete [] m_data;
m_length = size;
m_data = temp;
}
void insertBefore(const int& value, const int& index) {
assert(index < m_length+1);
resize(m_length+1);
for (int i = m_length-1; index<i; --i) {
m_data[i] = m_data[i-1];
}
m_data[index] = value;
}
void remove(const int &index) {
assert(index<m_length);
int *temp = new int[m_length-1];
for (unsigned int i = 0; i < index; ++i) {
temp[i] = m_data[i];
}
for (unsigned int i = index; i<m_length-1; ++i) {
temp[i] = m_data[i+1];
}
delete [] m_data;
m_data = temp;
m_length = m_length-1;
}
void push_back(const int& value) {
resize(m_length+1); // ++m_length;
m_data[m_length-1] = value;
}
friend ostream& operator << (ostream& out, const IntArray& intarray) {
cout << "size = " << intarray.m_length << " add=" << intarray.m_data << " ";
for(int i=0; i< intarray.m_length; ++i)
out << intarray.m_data[i] << " ";
return out;
}
};
int main() {
IntArray test{ 0,1,2,3 };
cout << test << endl;
test.remove(0);
cout << test << endl;
test.insertBefore(-1,0);
cout << test << endl;
test.push_back(10);
cout << test << endl;
test.reset();
cout << test << endl;
test.insertBefore(100,0);
cout << test << endl;
}
배운점
- 아래 << 연산자 오버로딩을 구현할 때 IntArray를 참조로 가져오지 않아 얕은복사가 실행되었다. 이 함수가 종료되고 나면 소멸자 호출된다. 얕은복사가 된 IntArray 내부 동적할당 메모리가 해제된다. 나중에 원본이 삭제될 차례가 되면 이미 해체된 동적할당 메모리를 다시 한 번 해제하게 되므로 에러가 발생한다.
friend ostream& operator << (ostream& out, IntArray intarry) {
for(int i=0; i<intarry.m_length; ++i)
out << intarry.m_data[i] << " ";
return out;
}
정리
'Programming Language > C++' 카테고리의 다른 글
[홍정모의 따라하며 배우는 C++] 12. 가상함수들 (0) | 2021.12.28 |
---|---|
[홍정모의 따라하며 배우는 C++] 11. 상속 (0) | 2021.12.27 |
[홍정모의 따라하며 배우는 C++] 9.연산자 오버로딩 (0) | 2021.12.25 |
[홍정모의 따라하며 배우는 C++] 8. 객체지향의 기초 (0) | 2021.12.24 |
[홍정모의 따라하며 배우는 C++] 7. 함수 (0) | 2021.12.23 |