** 기능의 처리를 실제로 담당하는 클래스를 가리켜 '컨트롤(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://has3ong.github.io/programming/c-virtualfunction/
** Up Casting
: 자식 클래스(=유도 클래스)의 참조 또는 포인터를 부모 클래스(= 기초 클래스)의 참조 또는 포인터로 변환
Down Casting
: 부모 클래스(=기초 클래스)의 참조 또는 포인터를 명시적인 형변환을 통해 자식 클래스(=유도 클래스)로 변환
https://devji.tistory.com/entry/C-Upcasting-Downcasting-Virtual-Override
** 가상 소멸자(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++ 프로그래밍 도서를 읽고 정리한 글입니다.
'IT공부 > IT서적' 카테고리의 다른 글
[윤성우 열혈 C++프로그래밍] Part4. 객체지향의 완성 - 2 (0) | 2024.09.24 |
---|---|
[윤성우 열혈 C++프로그래밍] Part4. 객체지향의 완성 - 1 (0) | 2024.09.21 |
[윤성우 열혈 C++프로그래밍] Part2. 객체지향의 도입 (0) | 2024.08.15 |
[윤성우 열혈 C++프로그래밍] Part1. c++로의 전환 (0) | 2024.08.08 |
[윤성우 열혈 C프로그래밍] Part4. C언어의 깊은 이해 (0) | 2024.07.28 |