C++ Chapter 10.3 : 집합 관계

Date:     Updated:

카테고리:

태그:

인프런에 있는 홍정모 교수님의 홍정모의 따라 하며 배우는 C++ 강의를 듣고 정리한 필기입니다. 😀
🌜 [홍정모의 따라 하며 배우는 C++]강의 들으러 가기!


chapter 10. 객체들 사이의 관계 : 집합 관계

관계 종류 관계를 표현하는 동사 관계 형태 다른 클래스에 속할 수 있는가? 멤버의 존재를
클래스가 관리 하는가?
방향성
집합 Has-a 전체/부품 Yes No 단방향


🔔 구성 관계의 부작용

  • 학생 한명이 여러 강의를 수강할 수 있으므로 서로 다른 강의 객체에 속해있어도 동일한 학생이라면 동기화 해야한다.
  • 그러나 강의-학생 객체끼리의 관계가 구성 관계라면 학생은 다른 강의 객체에 속할 수 없으므로 동기화에 있어 문제가 생긴다.

구성 관계의 단점은 동일한 의미를 가지는 객체에 대해서 각각 다른 객체가 속해(구성)있더라도 동기화가 불가능하다는 점이다


코드 구조 설명

  • 📜Lecture 클래스
    • 멤버
      • string m_name : 강의 이름
      • Teacher teacher : 강의 담당 교사 객체
      • std::vector<Student> students : 수강하는 학생들 객체 벡터


📜Student.h

#pragma once

#include <iostream>
#include <string>

class Student
{
private:
	std::string m_name;
	int m_intel; 

public:
	Student(const std::string & name_in = "No Name", const int & intel_in = 0)
		: m_name(name_in), m_intel(intel_in)
	{}

	void setName(const std::string & name_in)
	{
		m_name = name_in;

	}

	void setIntel(const int & intel_in)
	{
		m_intel = intel_in;
	}

	int getIntel()
	{
		return m_intel;
	}

	friend std::ostream & operaotr << (std::ostream & out, const Student & student)
	{
		out << student.m_name << " " << student.m_intel;
		return out;
	}
};
  • 멤버
    • string m_name : 학생 이름
    • int m_intel : 학생 지능


📜Teacher.h

#pragma once

#include <string>

class Teacher
{
private:
	std::string m_name;

public:
    Teacher()
    {}

	Teacher(const std::string & name_in)
		: m_name(name_in)
	{}

	void setName(const std::string & name_in)
	{
		m_name = name_in;
	}

	std::string getName()
	{
		return m_name;
	}

	friend std::ostream & operaotr << (std::ostream & out, const Teacher & teacher)
	{
		out << teacher.m_name;
		return out;
	}
};
  • 멤버
    • string m_name : 교사 이름


📜Lecture.h

#pragma once

#include <vector>
#include "Student.h"
#include "Teacher.h"

class Lecture
{
private:
	std::string m_name;

	Teacher teacher;
	std::vector<Student> students; 

public:
	Lecture(const std::string & name_in)
		: m_name(name_in)
	{}

	void assignTeacher(const Teacher & const teacher_input)
	{
		teacher = teacher_input;
	}

	void registerStudent(const Student & const student_input)
	{
		students.push_back(student_input);
	}

	void study()
	{
		std::cout << m_name << " Study " << std::endl;

		for (auto & element : students)
			element.setIntel(element.getIntel() + 1);
	}

	friend std::ostream & operator << (std::ostream & out, const Lecture & lecture)
	{
		out << "Lecture name: " << lecture.m_name << std::endl;

		out << lecture.teacher << std::endl;
		for (auto element : lecture.students)
			out << element << std::endl;

     return out;
	}
};
class Lecture
{
private:
	std::string m_name;

	Teacher teacher;
	std::vector<Student> students; 

Lecture객체가 사라지면 그에 속한 Teacher 객체 멤버와 Student 벡터 객체 멤버도 함께 사라진다. 구성 관계 이기 때문

  • 멤버 변수
    • string m_name : 강의 이름
    • Teacher teacher : 강의 담당 교사 객체
      • #include “Teacher.h”
    • std::vector<Student> students : 수강하는 학생들 객체 벡터
      • #include “Student.h”
  • 멤버 함수
    • void assignTeacher(const Teacher & const teacher_input)
      • 해당 강의에 교사를 배정함
        • 인수로 받은 교사 객체를 해당 강의의 교사 멤버에 대입함
    • void registerStudent(const Student & const student_input)
    • 해당 강의에 학생을 배정함
      • 인수로 받은 학생 객체를 해당 강의의 학생 벡터 멤버에 삽입 추가한다.
    • void study()
      • 자기 자신(강의 객체)을 수강하면 해당 강의를 들은 학생들의 m_intel 을 1 올린다. 즉, 지능 증가!
        • for문 돌려 벡터안에 든 개개인의 학생 객체들 m_intel 을 1 올린다.
          • & 안붙이면, 즉 참조 안하면 반영 안됨.


예시 1

📜main.cpp

#include <iostream>
#include <vector>
#include <string>
#include "Lecture.h"

int main()
{
	using namespace std;

	//composition relationship
	Lecture lec1("C++");
	lec1.assignTeacher(Teacher("Prof. Hong"));
	lec1.registerStudent(Student("Jack Jack", 0));
	lec1.registerStudent(Student("Dash, 1"));
	lec1.registerStudent(Student("Violet", 2));

	Lecture lec2("Java");
	lec2.assignTeacher(Teacher("Prof. Good"));
	lec2.registerStudent(Student("Jack Jack", 0));

	{
		cout << lec1 << endl;
		cout << lec2 << endl;

		lec2.study();

		cout << lec1 << endl;
		cout << lec2 << endl;
	}

	return 0;
}

“Jack Jack” 학생은 Java 수업과 C++ 수업을 동시에 수강하고 있기 때문에 lec2.study(); Java 수업을 공부한 후에는 “Jack Jack” 학생 객체의 m_intel이 1 증가해야 한다. 그러나 출력결과를 보면 lect1의 “Jack Jack” 학생은 m_intel값이 여전히 0 이고 lect2의 “Jack Jack” 학생만 m_intel값이 1 증가한 것을 알 수 있다. 즉 lect1의 “Jack Jack”객체와 Java++ 수업 객체 lect2의 “Jack Jack”객체는 서로 다른 존재이기 때문에 Java 수업을 수강했더라도 lect2의 “Jack Jack”객체에만 지능이 증가했다.

문제점

image

  • C++ 수업 객체 lect1의 “Jack Jack”객체와 Java++ 수업 객체 lect2의 “Jack Jack”객체는 서로 다른 존재이다.
    • 멤버 값이 단순히 같을 뿐, 즉 그냥 내용물만 같을 뿐이다.
    • C++ 수업을 듣는 “Jack Jack”과 Java 수업을 듣는 “Jack Jack”는 동일 인물 취급을 받아야 한다. 즉, 주소가 같은 동일한 객체여야 한다.

학생 객체 내용물을 Lec1, Lec2 강의 객체에 구성 요소로 속해선 안된다. 학생 객체들은 각각 여러 강의에 속할 수 있어야 하기 때문에 강의와 학생은 집합 관계로 이루어져야 한다. 학생이 교집합으로서 이 강의 저 강의에도 속할 수 있도록!


예시 2

학생 객체들과 선생님 객체들을 독립적으로 미리 생성해둔 후 이를 인수로 넘긴다면 어떨까?

📜main.cpp

#include <iostream>
#include <vector>
#include <string>
#include "Lecture.h"

int main()
{
	using namespace std;

    Student std1("Jack Jack", 0);
	Student std2("Dash", 1);
	Student std3("Violet", 2);

	Teacher teacher1("Prof. Hong");
	Teacher teacher2("Prof. Good");

	//composition relationship
	Lecture lec1("C++");
	lec1.assignTeacher(teacher1);
	lec1.registerStudent(std1);
	lec1.registerStudent(std2);
	lec1.registerStudent(std3);

	Lecture lec2("Java");
	lec2.assignTeacher(teacher2);
	lec2.registerStudent(std1);

	{
		cout << lec1 << endl;
		cout << lec2 << endl;

		lec2.study();

		cout << lec1 << endl;
		cout << lec2 << endl;
	}

	return 0;
}
  • 출력 결과를 보면 알 수 있지만 객체들을 독립적으로 미리 생성해둔 후 이를 인수로 넘겨주더라도 여전히 문제는 해결되지 않았다.
    • 여전히 C++의 “Jack Jack”과 Java의 “Jack Jack”은 별개의 객체이다.


문제점

객체들을 독립적으로 미리 생성해둔 후 이를 인수로 넘겨주더라도 인수가 벡터 멤버에 Call-by-Value 로 복사 되기 때문이다.

image

void registerStudent(const Student & const student_input)
{
	students.push_back(student_input);
}

...
lec1.registerStudent(std1);
lec2.registerStudent(std1);

image

  • std1이 인수로 넘어가면서 매개 변수 student_inputstd1 메모리를 참조하게 된다.
    • const Student & const 타입이므로.
  • 벡터에 student_input, 즉 std1 객체가 삽입되는 과정에서 내부적으로 lect1.students[0] = student_input 되며 Call by Value 복사가 이루어진다.
    • lect1.students[0]std1 객체는 서로 다른 존재이다. 내용물만 같을뿐
    • 고로 lect1.students[0]lect2.students[0]도 서로 다른 존재이다. 내용물만 같을뿐

따라서 동일한 객체를 인수로 넘겨주어도 Call by Value로 복사가 이루어지기 때문에 “Jack Jack”이 동기화 되지 않는 문제가 여전히 해결되지 않았다.

학생 개개인의 객체 내용물은 구성관계로서 강의 객체에 소속되지 않아야 한다. 그 내용물을 가리키는 포인터만 강의 객체에 소속되고 내용물은 강의 객체에 독립적이어야 한다. 각각 다른 강의 객체에도 속하는게 가능하도록!


🔔 집합 관계 : 포인터 사용하기

집합 관계를 나타내는 코드

📜Lecture.h

#include <vector>
#include "Student.h"
#include "Teacher.h"

class Lecture
{
private:
	std::string m_name;

	Teacher *teacher;
	std::vector<Student*> students;

public:
	Lecture(const std::string & name_in)
		: m_name(name_in)
	{}

	~Lecture()
	{
		delete teacher;
		cout << "delete teacher" << endl;
		
		for (auto & element : students)
		{
		    delete element;
		}
		cout << "delete students" << endl;
	}

	void assignTeacher(Teacher * const teacher_input)
	{
		teacher = teacher_input;
	}

	void registerStudent(Student * const student_input)
	{
		students.push_back(student_input);
	}

	void study()
	{
		std::cout << m_name << "Study " << std::endl << std::endl;

		for (auto element : students)
			(*element).setIntel((*element).getIntel() + 1);
	}

	friend std::ostream & operator << (std::ostream & out, const Lecture & lecture)
	{
		out << "Lecture name: " << lecture.m_name << std::endl;

		out << lecture.teacher << std::endl;
		for (auto element : lecture.students)
			out << *element << std::endl;

     return out;
	}
};

내용물강의 객체에 구성(소속)되지 않게 하고내용물의 주소를 담은 포인터강의 객체에 구성(소속)되게 한다.

  • 멤버를 객체 포인터로 바꾸었다.
    • Teacher *teacher;
    • std::vector<Student> students;*
  • 소멸자
    • 객체 포인터에다 학생, 교사 객체의 내용물은 동적할당 받을 것이기 때문에 강의 객체가 소멸될 때 학생, 교사 객체의 내용물이 메모리 해제될 수 있도록 delete를 해주었다.
    • 학생은 객체 포인터 벡터안에 담겨있으므로 for문 돌려 모든 원소들을 delete 시켜준다.
      • 벡터 안엔 주소가 담겨 있으므로 for-each 문에서 &를 붙일 필요는 없다. (붙여도 상관 없지만)
  • void registerStudent(Student * const student_input)
    • 일반 객체로 인수를 넘겼을 때는 const Student * const 타입으로 받았으나 포인터를 인수로 넘길 때는 앞에 const를 빼고 Student * const 타입의 매개변수로 받는다.
      • 강의를 study() 할 때 학생 객체의 멤버 m_intel 값을 간접 참조로 증가시켜야 하기 때문이다
        1. const 포인터는 가리키는 대상을 간접참조로 값을 변경할 수 없다.
        2. 일반 포인터 타입에 const 포인터 타입을 대입할 수는 없다.
        • int * = const int * 는 불가능하다.
        • 주소값을 대입받는 일반 포인터 변수가 간접참조로 값을 변경할 우려가 있기 때문이다.
        • Student * 타입인 벡터에 Const Student * 타입의 포인터를 복사하여 넘겨줄 순 없다.
        • 그냥 참조 타입 변수에 const 참조 타입 변수를 대입하는건 단순히 값을 복사하는 것이기 때문에 괜찮지만 포인터는 주소값을 복사해서 넘겨주는 과정에서 const가 아닌 일반 포인터가 간접참조로 값을 변경할 것을 우려하기 때문에 일반 포인터에 const 포인터를 할당하는건 막혀있다.
        • char * 타입의 변수에 const char * 즉, 문자열 상수를 대입할 수 없는 것도 이와 같은 이유.
        • 참고 포스트: 포인터와 const
          int a = 3;
          int b = 7;
          
          int * ptr1;
          const int * ptr2 = &a;
          
          ptr1 = ptr2;  // ⚡⚡에러⚡⚡ 
          // const int* 타입인 ptr2를 그냥 int* 인 ptr1에 대입할 수 없음.
          // a의 주소값을 복사받은 ptr1이 a의 값을 간접참조로 바꿀 수도 있기 때문에
                  
          int c = 300;
          int d = 700;
          
          const int & ref2 = d;
          int & ref1 = c;
          
          ref1 = ref2;   // 문제 없음
          
  • void study()
    • for (auto element : students)
      • students 벡터엔 Students타입의 객체들을 가리키는 포인터들이 들어있기 때문에 참조 하지 않고 그냥 &떼고 auto element라고만 해도 된다.
    • 간접 참조로 m_intel 1 증가시키기
      • (*element).setIntel((*element).getIntel() + 1);
  • 출력 연산자 오버로딩
    • 간접 참조로 가리키는 내용물 출력하기
    • 그냥 element는 주소값이므로
      • out « *element « std::endl;


📜main.cpp

#include <iostream>
#include <vector>
#include <string>
#include "Lecture.h"

int main()
{
	using namespace std;

	// 같은 학생이나 교수를 재생성하지 않도록 
	Student *std1 = new Student("Jack Jack", 0);
	Student *std2 = new Student("Dash", 1);
	Student *std3 = new Student("Violet", 2);

	Teacher *teacher1 = new Teacher("Prof. Hong");
	Teacher *teacher2 = new Teacher("Prof. Good");

	{
		cout << lec1 << endl;
		cout << lec2 << endl;

		//event
		lec2.study();

		cout << lec1 << endl;
		cout << lec2 << endl;
	}
}

image

  • 각 학생과 교사 객체를 동적할 당받아 그 주소를 담는 포인터들을 선언한다.
    • 그리고 이들을 인수로 넘긴다.
  • C++의 “Jack Jack”과 Java의 “Jack Jack”은 동일한 주소값을 가진다.
    • 따라서 그 주소를 통해 간접 참조하면 동일한 학생 객체 "Jack Jack"에 접근할 수 있다.

집합 관계 : 학생 객체의 주소값을 담는 포인터 멤버 teacher, studentsLecture객체가 사라지면 함께 사라지는 구성요소지만 그 포인터 멤버가 가리키는 Teacher, Student 타입의 객체(내용물)들은 Lecture객체가 사려졌다고 해서 같이 사라지는 건 아니다. 구성 요소가 아님. 독립적인 객체로서 이곳 저곳 다른 객체와도 연결될 수 있다.


집합 관계 설명

Has-a

구성 관계보다 느슨한 전체/부품 관계이다. 집합처럼 다른 클래스에도 속할 수 있다.

  • 사람은 자동차를 가질 수 있지만 자동차가 사라져도 사람은 존재한다. 또한 사람이 사라져도 자동차는 존재한다.
  • 다른 클래스에 속할 수 있는가? : Yes
    • 포인터는 구성요소지만
    • 포인터 std1가리키는 객체(Jack Jack)lec1에도 속하고 lec2에도 속한다고 볼 수 있다.
  • 멤버의 존재를 클래스가 관리 하는가? : No
    • lec1, lec2이 사라진다고 해서 std1가리키는 객체(Jack Jack)도 사라지는건 아니다.
  • 단방향
    • lec1, lec2을 통해서 std1이 가리키는 객체(Jack Jack)에 접근할 수 있다.


cf

분산처리시에는 여러 컴퓨터들이 메모리를 분리해서 쓰기 떄문에 이를 공유하는데 있어 동기화 OR 사본화을 잘 숙지해야 한다.



🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기


Cpp 카테고리 내 다른 글 보러가기

댓글 남기기