IT공부/IT서적

[윤성우 열혈 C++프로그래밍] Part3. 객체지향의 전개

shine94 2024. 9. 14. 06:11

** 기능의 처리를 실제로 담당하는 클래스를 가리켜 '컨트롤(control) 클래스' 또는 '핸들러(handler) 클래스'라 함

 

** 상속(Inheritance)

   [과거] 기존에 정의해 놓은 클래스의 재활용을 목적으로 만들어진 문법적 요소

   [새로운 관점] UnivStudent 클래스가 Person 클래스를 상속한다는 전제

                         UnivStudent 객체에는 UnivStudent 클래스에 선언되어 있는 멤버 뿐만 아니라

                         Person 클래스에 선언되어 있는 멤버도 존재하게 됨

#include <iostream>
using namespace std;

class Person
{
private:
	int age;
	char name[50];

public:
	Person(int myage, const char* myname) : age(myage)
	{
		strcpy_s(name, myname);
	}

	void WhatYourName() const
	{
		cout << "My name is " << name << endl;
	}
	void HowOldAreYou() const
	{
		cout << "I'm " << age << " years old" << endl;
	}
};

class UnivStudent : public Person
{
private:
	char major[50];
public:
	UnivStudent(const char* myname, int myage, const char* mymajor) 
		: Person(myage, myname)
	{
		strcpy_s(major, mymajor);
	}

	void WhoAreYou() const
	{
		WhatYourName();
		HowOldAreYou();
		cout << "My major is " << major << endl << endl;
	}
};

int main(void)
{
	UnivStudent str1("Lee", 22, "Computer eng.");
	str1.WhoAreYou();

	UnivStudent str2("Park", 31, "Electronic eng.");
	str2.WhoAreYou();

	cout << endl << endl;
	str2.WhatYourName();
	return 0;
}

 

** 부모 클래스 ↔ 자식 클래스

   상위 클래스 ↔ 하위 클래스

   기초(base) 클래스 ↔ 유도(derived) 클래스

   슈퍼(super) 클래스 ↔ 서브(Sub) 클래스

 

** 유도 클래스의 객체 생성 과정에서 기초 클래스의 생성자는 100% 호출

   [이때]  유도 클래스의 생성자에서 기초 클래스의 생성자 호출을 명시하지 않으면 기초 클래스의 기본 생성자가 호출

 

** 유도 클래스의 객체가 소명될 때에는, 유도 클래스의 소멸자가 실행되고 난 다음에 기초  클래스의 소멸자가 실행

   [위의 의미는] 스택에 생성된 객체의 소멸순서는 생성순서와 반대

#include <iostream>
#include <cstring>
using namespace std;

class Person
{
private:
	char* name;

public:
	Person(const char* myname)
	{
		name = new char[strlen(myname) + 1];
		strcpy_s(name, strlen(myname) + 1, myname);
	}
	~Person()
	{
		delete[]name;
	}

	void WhatYourName() const
	{
		cout << "My name is " << name << endl;
	}
};

class UnivStudent : public Person
{
private:
	char* major;
public:
	UnivStudent(const char* myname, const char* mymajor)
		: Person(myname)
	{
		major = new char[strlen(mymajor) + 1];
		strcpy_s(major, strlen(mymajor) + 1, mymajor);
	}
	~UnivStudent()
	{
		delete[]major;
	}

	void WhoAreYou() const
	{
		WhatYourName();
		cout << "My major is " << major << endl << endl;
	}
};

int main(void)
{
	UnivStudent str1("Lee", "Computer eng.");
	str1.WhoAreYou();

	UnivStudent str2("Park", "Electronic eng.");
	str2.WhoAreYou();
	return 0;
}

 

** 세 가지 형태의 상속

1) protected 상속 : protected보다 접근 범위가 넓은 경우 protected로 변경시켜서 상속

2) private 상속 : private보다 접근 범위가 넓은 경우 private으로 변셩시켜서 상속

3) public 상속 : private를 제외한 나머지는 그냥 그대로 상속

 

** 상속을 위한 조건

1) IS-A 관계

2) HAS-A 관계

 

** 객체 포인터의 참조관계

 : Person형 포인터는 Person 객체 뿐만 아니라, Person을 상속하는 유도 클래스의 객체도 가리킬 수 있음

   ㄴ 객체지향은 IS-A 관계의 성립으로 인해서 유도클래스의 객체까지 가리키는 게 말이 되는 것임

Person* ptr;		// 포인터 변수 선언

ptr = new Person();
ptr = new Student();

 

** C++ 컴파일러는 포인터를 이용한 연산의 가능성 여부를 판단할 때,

   포인터의 자료형을 기준으로 판단하지, 실제 가리키는 객체의 자료형 기준으로 판단하지 않음

   [문제] 함수를 오버라이딩 했다는 것은,

             해당 객체에서 호출되어야 하는 함수를 바꾼다는 의미인데,

             그렇기 때문에 포인터 변수의 자료형에 따라서 호출되는 함수의 종류가 달라지면 안됨

   [해결] 가상함수 사용

 

** 가상함수(Virtual Function)

 : 포인터 변수가 실제로 가리키는 객체를 참조하여 호출의 대상을 결정

 

** 순수 가상함수(Pure Virtual Function)

 : 함수의 몸체가 정의되지 않은 함수

   이를 표현하기 위해 0을 대입

class Employee
{
private:
	char name[100];
public:
	Employee(char* name) { ... }
	void ShowYourName() const { ... }
	virtual int GetPay() const = 0;		// 순수 가상함수
	virtual int ShowSalaryInfo() const = 0;	// 순수 가상함수
};

 

** 추상 클래스(Abstract Class)

 : 하나 이상의 멤버함수를 순수 가상함수로 선언한 클래스

   이는 완전하지 않은 그래서 객체 생성이 불가능한 클래스란 의미도 내포

 

** 다형성(Polymorphism) - 가상함수의 호출 관계

 : 모습은 같은데 형태는 다르다는 의미로 C++에서는 문장은 같은데 결과는 다르다는 말

 

https://gdngy.tistory.com/178

 

[C/C++ 프로그래밍 : 중급] 6. 다형성

Chapter 6. 다형성 다형성(Polymorphism)은 동일한 인터페이스에서 다양한 동작을 할 수 있는 객체 지향 프로그래밍의 핵심 특성 중 하나입니다. 이 챕터에서는 C++에서의 다형성 개념을 이해하고 이를

gdngy.tistory.com

https://has3ong.github.io/programming/c-virtualfunction/

 

C++ 가상함수 / 순수 가상함수 차이

가상함수

has3ong.github.io

 

** Up Casting

    : 자식 클래스(=유도 클래스)의 참조 또는 포인터를 부모 클래스(= 기초 클래스)의 참조 또는 포인터로 변환

    Down Casting

    : 부모 클래스(=기초 클래스)의 참조 또는 포인터를 명시적인 형변환을 통해 자식 클래스(=유도 클래스)로 변환

 

https://devji.tistory.com/entry/C-Upcasting-Downcasting-Virtual-Override

 

[C++] Upcasting, Downcasting, Virtual, Override

자식 클래스의 참조 또는 포인터를 부모 클래스의 참조, 포인터로 변환하는 것을 Upcasting이라 합니다. 반대로 부모 클래스의 참조, 포인터를 명시적인 형변환을 통해 자식 클래스로 변환하는 것

devji.tistory.com

 

** 가상 소멸자(Virtual Destructor)

[필요한 이유] 업캐스팅인 경우, 기초 클래스의 소멸자만 호출되어 메모리 누수(leak)가 발생

                      객체의 소멸 과정에서는 모든 소멸자가 호출되어야 함

[해결] 가상 소멸자를 이용

          가상 소멸자가 호출되면, 

          상속의 계층구조상 맨 아래에 존재하는 유도 클래스의 소멸자가 대신 호출되면서,

          기초 클래스의 소멸자 순으로 순차적으로 호출

[사용] 소멸자도 상속의 계층구조상 맨 위에 존재하는 기초 클래스의 소멸자만 virtual로 선언하면,

          이를 상속하는 유도클래스의 소멸자들도 모두 가상 소멸자로 선언되기 때문에

          별도로 virtual 선언을 추가하지 않아도 됨

 

▼ Before - 문제 있음

#include <iostream>
using namespace std;

class First
{
private:
	char* strOne;
public:
	First(const char* str) 
	{
		strOne = new char[strlen(str) + 1];
	}

	~First()
	{
		cout << "~First()" << endl;
		delete[] strOne;
	}
};

class Second : public First
{
private:
	char* strTwo;
public:
	Second(const char* str1, const char* str2) : First(str1)
	{
		strTwo = new char[strlen(str2) + 1];
	}

	~Second()
	{
		cout << "~Second()" << endl;
		delete[] strTwo;
	}
};

int main(void)
{
	First* ptr = new Second("simple", "complex");
	delete ptr;
	return 0;
}

 

 

▼After - 문제 해결

#include <iostream>
using namespace std;

class First
{
private:
	char* strOne;
public:
	First(const char* str) 
	{
		strOne = new char[strlen(str) + 1];
	}

	virtual ~First()
	{
		cout << "~First()" << endl;
		delete[] strOne;
	}
};

class Second : public First
{
private:
	char* strTwo;
public:
	Second(const char* str1, const char* str2) : First(str1)
	{
		strTwo = new char[strlen(str2) + 1];
	}

	~Second()
	{
		cout << "~Second()" << endl;
		delete[] strTwo;
	}
};

int main(void)
{
	First* ptr = new Second("simple", "complex");
	delete ptr;
	return 0;
}

 

 

** 객체 내에 멤버함수가 존재한다고 이전에는 설명했으나, 이건 사실이 아님

   ㄴ 객체가 생성되면

       멤버변수는 객체 내에 존재하지만

       멤버함수는 메모리의 한 공간에 별도로 위치하고선 이 함수가 정의된 모든 객체가 이를 공유하는 형태를 취함

 

** 가상함수는 가상함수 테이블에 참조되는 방식

   ㄴ 한 개 이상 가상함수를 포함하는 클래스에 대해서는 컴파일러가

        가상함수 테이블(V-Table, Virtual Table: 실제 호출되어야 할 함수의 위치정보를 담고 있는 테이블)을 만듦

 

** 다중상속(Multiple Inheritance)

 : 둘 이상의 클래스를 동시에 상속하는 것

 

** 다중상속의 모호성(Ambiguous)

 : 다중상속의 대상이 되는 두 기초 클래스에 동일한 이름의 멤버가 존재 → 문제 발생

   [해결] 범위 지정 연산자 사용

 

** 가상 상속(Virtual Inheritance)

▼ Before - 문제 있음

 

▼ After - 문제 해결, 가상함수 씀

 

 

** 해당 글은 윤성우의 열혈 C++ 프로그래밍 도서를 읽고 정리한 글입니다.

   https://product.kyobobook.co.kr/detail/S000001589147

 

윤성우의 열혈 C++ 프로그래밍 | 윤성우 - 교보문고

윤성우의 열혈 C++ 프로그래밍 | 『C++ 프로그래밍』은 C언어를 이해하고 있다는 가정하에서 집필된 C++ 기본서로서, 초보자에게 적절한 설명과 예제를 통해서 C++ 프로그래밍에 대해 설명한다. 더

product.kyobobook.co.kr