- a 와 has - aclass Manager : public Employee
Manager가 Employee를 상속한다.Manager 클래스는 Employee 의 모든 기능을 포함한다.Manager 클래스는 Employee의 모든 기능을 수행할 수 있기 때문에,Manager를 Employee라고 칭해도 무방하다.따라서, 모든 상속 관계는 is a관계라고 볼 수 있음. (하지만 뒤바꾸면 성립되지 X)

상속 관계 도표로 표현 시, 파생 클래스가 기반 클래스를 화살표로 가리킴.
상속의 특징
1. 특수화 (구체화 : specialize)
Employee는 일반적 사원 Manager는 일반적 사원 중 특수한 사원has - a 관계파생 클래스에서 기반 클래스로 캐스팅하는 형태
Base p;
Derived c;
// Derived 의 객체 c 를 Base 객체를 가리키는 포인터에 넣음
Base* p_c = &c;

Derived가 Base를 상속 받았으므로, Base 객체를 가리키는 포인터가 Derived c객체를 가리켜도 무방함. 단, p는 Base 객체를 가리키는 포인터이므로 Base class에 있는 것만 사용

기반 클래스에서 파생 클래스로 캐스팅하는 형태
// error 발생
Base p;
Derived c;
Derived* p_p = &p;

Base p;
Derived c;
Base* p_p = &c;
Derived* p_c = p_p;
p_c->what();
위 코드에서도 마찬가지로 에러 발생
// dynamic_cast
Derived* p_c = static_cast<Derived*>(p_p);
위와 같이 강제적으로 타입 변환하면 컴파일 오류 발생하지는 않지만 권장X
#include <iostream>
class Base {
public:
Base() { std::cout << "기반 클래스" << std::endl; }
virtual void what() { std::cout << "기반 클래스의 what()" << std::endl; }
};
class Derived : public Base {
public:
Derived() : Base() { std::cout << "파생 클래스" << std::endl; }
void what() { std::cout << "파생 클래스의 what()" << std::endl; }
};
int main() {
Base p;
Derived c;
Base* p_c = &c;
Base* p_p = &p;
std::cout << " == 실제 객체는 Base == " << std::endl;
// 기반 클래스의 what()
p_p->what();
std::cout << " == 실제 객체는 Derived == " << std::endl;
// 파생 클래스의 what()
p_c->what();
return 0;
}
위의 코드에서 p_p와 p_c는 모두 Base를 가리키는 포인터지만, 실제 p_p 와 p_c 가 무엇과 결합해 있는지 아는 것 처럼 p_p 는 Base 객체를 가리키고, p_c 는 Derived 객체를 가리킴. == virtual 키워드 때문!
여기서 virtual 키워드는, 동적 바인딩 실행.
즉, p_c->what(); 에서 아래의 순서로 작동되는 것!
1. p_c는 Base 포인터니까 Base의 what()을 실행해야지
2. 근데 what이 virtual 이네?
3. 이거 Base의 객체가 맞나?
4. 아니 Derived의 객체네
5. 그럼 Derived의 what을 실행해야지!
클래스 상속을 사용할 때, 소멸자를 가상함수로 만들어야 된다는 점을 중요하게 처리해야 함
#include <iostream>
class Parent {
public:
Parent() { std::cout << "Parent 생성자 호출" << std::endl; }
virtual ~Parent() { std::cout << "Parent 소멸자 호출" << std::endl; }
};
class Child : public Parent {
public:
Child() : Parent() { std::cout << "Child 생성자 호출" << std::endl; }
~Child() { std::cout << "Child 소멸자 호출" << std::endl; }
};
int main() {
std::cout << "--- 평범한 Child 만들었을 때 ---" << std::endl;
{
// 이 {} 를 빠져나가면 c 가 소멸된다.
Child c;
}
std::cout << "--- Parent 포인터로 Child 가리켰을 때 ---" << std::endl;
{
Parent *p = new Child();
delete p;
}
}
Q) https://modoocode.com/211 주석부분 차이?
Parent 의 소멸자를 virtual 로 만들면, p 가 소멸자를 호출할 때, Child 의 소멸자를 성공적으로 호출할 수 있음.
기반 클래스에서 파생 클래스 함수에 접근하는 2가지 방법
#include <iostream>
class A {
public:
virtual void show() { std::cout << "Parent !" << std::endl; }
};
class B : public A {
public:
void show() override { std::cout << "Child!" << std::endl; }
};
void test(A& a) { a.show(); }
int main() {
A a;
B b;
test(a);
test(b);
return 0;
}
함수에 타입이 기반 클래스여도 그 파생 클래스는 타입 변환되어 전달할 수 있음.
따라서 test함수에서 show()를 호출했을 때, 인자로 b를 전달했다면, 비록 전달된 인자가 A의 객체이지만 show()가 virtual로 정의되어 있끼 때문에 알아서 B.show()를 찾아서 호출
C++ 에서 사용자가 직접 virtual 로 선언하도록 한 이유?
class Parent {
public:
virtual void func1();
virtual void func2();
};
class Child : public Parent {
public:
virtual void func1();
void func3();
};
C++ 컴파일러는 가상 함수가 하나라도 존재하는 클래스에 대해서, 가상 함수 테이블(virtual function table; vtable)을 만듦

가상 함수를 호출하였을 때는 가상 함수 테이블을 한 단계 더 걸쳐서, 실제로 어떤 함수를 고를지 결정
순수 가상 함수
class Animal {
public:
Animal() {}
virtual ~Animal() {}
virtual void speak() = 0;
};
= 0;을 붙여서, 반드시 오버라이딩되도록 만든 함수Animal a; a.speak(); 불가능!추상 클래스
class Dog : public Animal {
public:
Dog() : Animal() {}
void speak() override { std::cout << "왈왈" << std::endl; }
};
한 클래스가 다른 여러 개의 클래스들을 상속 받는 것을 허용하는 형태
class A {
public:
int a;
};
class B {
public:
int b;
};
class C : public A, public B {
public:
int c;
};

위의 경우, 단순히 A와 B의 내용이 모두 C에 들어가는 것.
#include <iostream>
class A {
public:
int a;
A() { std::cout << "A 생성자 호출" << std::endl; }
};
class B {
public:
int b;
B() { std::cout << "B 생성자 호출" << std::endl; }
};
class C : public A, public B {
public:
int c;
C() : A(), B() { std::cout << "C 생성자 호출" << std::endl; }
};
int main() { C c; }

A -> B -> C 순서로 호출됨.
그러나 아래와 같이 코드 바꾼다면,
class C : public B, public A
B -> A -> C 순서로 호출됨.
즉, 오직 상속하는 순서에 생성자 호출 순서가 좌우되는 것.

virtual로 상속 받는다면 이러한 문제 해결 가능