is - a 와 has - a 관계
오버라이딩(overriding)
virtual 키워드와 가상함수(virtual function)
다형성(polymorphism)
단순 코드 반복을 줄이려고 상속개념 도입보단.
상속이라는 기능을 통해 객체지향프로그래밍에서 추구하는 실제 객체의
추상화를 좀더 효과적으로 할수있게끔 하려고
무슨말이냐면 상속이 없던 C언어에서는 어떤 구조체들의 사이의 관계를 표현할수있는 방법이 없었다.
C++에서 상속을 도입함으로써 클래스 사이의 관계를 표현할수있는데
예를들어
class Manager : public Employee
의 의미는,
이렇게 파생클래스가 기반 클래스를 가리키게 그린다.
ex). 사람 <- 프로그래머 , 만약 프로그래머 클래스를 만든다면 사람이라는 클래스를 상속받을수 있도록 구성할수 있다.
상속의 중요한 특징
Q:그렇다면 모든 클래스들의 관계를 is - a
로만 표현할수 있을까?
A: Nope
어떤 클래스들 사이에서는 has-a
관계가 성립하기도 한다.
예) 자동차 클래스 를 구성하기 위해 엔진 클래스 , 브레이크 클래스, ...
이런 has-a 클래스 관계
class Car
{
private:
Egine e;
Brake b;
...
};
또 다른 예로
class EmployeeList {
int alloc_employee; // 할당한 총 직원 수
int current_employee; // 현재 직원 수
Employee **employee_list; // 직원 데이터
#include <iostream>
#include <string>
class Base
{
std::string s;
public:
Base():s("기반") {std::cout << "기반 클래스 " << std::endl;}
void what() {std::cout << s << std::endl;}
};
class Derived : public Base
{
std::string s;
public:
Derived(): Base(),s("파생") { std::cout << " 파생클래스" << std::endl;}
void what() { std::cout << s << std::endl;}
};
int main()
{
std::cout << " === 기반 클래스 생성 === " << std::endl;
Base p;
p.what();
std::cout << " === 파생 클래스 생성 === " << std::endl;
Derived c;
c.what();
std::cout << " === 포인터 버젼 === " << std::endl;
Base* p_c = &c;
p_c->what();
return 0;
}
>>
=== 기반 클래스 생성 ===
기반 클래스
기반
=== 파생 클래스 생성 ===
기반 클래스
파생클래스
파생
=== 포인터 버젼 ===
기반
Base* p_c =&c;
Derived 의 객체 c 를 Base 객체를 가리키는 포인터에 넣었다.
벗!.
p는 Base 객체를 가리키는 포인터이다.
따라서 p의 what 을 실행하면 Base의 what함ㅎ수를 실행한다.
what 은 Base의 s 를 출력하게 된다.
따라서 '기반' 이 출력된다.
이러한 캐스팅을 , 즉 파생 클래스에서 기반 클래스로 캐스팅하는것을 업 캐스팅 이라고 부릅니다.
다운캐스팅
int main()
{
Base p;
Derived c;
std::cout << "=== 포인터 버전 ===" << std::endl;
Derived* p_p = &p;
p_p->what();
return 0;
}
에러발생한다.
이유는
int main() {
Base p;
Derived c;
std::cout << "=== 포인터 버전 ===" << std::endl;
Base* p_p = &c;
Derived* p_c = p_p;
p_c->what();
return 0;
}
error C2440: 'initializing' : cannot convert from 'Base ' to 'Derived '
Derived p_c 에 Base 를 대입하면 안 된다는 오류
p_p 가 가리키는것이 Base 객체가 아니라 Dervied 객체라는 사실을 알고 있다.
그렇게 때문에 Base * 포인터를 다운캐스팅 함에도 불구하고 p_p가 실제로는 Derived객체를 가르키기 때문에
Dervied* p_c = p_p;
Derived* p_c = static_cast<Dervied*>(p_p)
만약 p_p가 사실 Base객체를 가리키는 데 강제적으로 타입 변환 해서 what 실행하면 ??
int main() {
Base p;
Derived c;
std::cout << "=== 포인터 버전 ===" << std::endl;
Base* p_p = &p;
Derived* p_c = static_cast<Derived*>(p_p);
p_c->what();
return 0;
}
#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;
p_p->what();
std::cout << " == 실제 객체는 Derived == " << std::endl;
p_c->what();
return 0;
}
>>
기반 클래스
기반 클래스
파생 클래스
== 실제 객체는 Base ==
기반 클래스의 what()
== 실제 객체는 Derived ==
파생 클래스의 what()
p_c 와 p_p 모두 Base 객체를 가리키는 포인터.
따라서 p_c->what() , p_p->what() 둘다 Base에서 호출 되어야한다.
그런데 실제 p_p와 p_c가 각각 Base, Dervied 와 결합했는지 아는것처럼 적절한 what함수를 호출한다
그게 가능한이유는 바로
class Base {
public:
Base() { std::cout << "기반 클래스" << std::endl; }
virtual void what() { std::cout << "기반 클래스의 what()" << std::endl; }
};
이 virtual 키워드 하나 때문이다.
p_c->what();
p_p->what();
- 이렇게 컴파일 시에 어떤 함수가 실행될지 정해지지 않고
- 런타임 시에 정해지는 일을 가리켜서 동적 바인딩(dynamic binding)이라 부른다
ex)
// i 는 사용자로부터 입력받는 변수
if (i == 1) {
p_p = &c;
} else {
p_p = &p;
}
p_p->what();
정적 바인딩 은 컴파일에서 어떤 함수가 호출될지 정해지는것.
가상함수 는 파생클래스의 함수가 기반 클래스의 함수를 오버라이드 하기 위해서는 두 함수의 꼴이 정확히 같아야한다.
기반클래스에서 virtual로 선언한 함수는 파생클래스에 있는 같은 시그니처의 함수들에게 자동으로 virtual로 선언됨!
override 키워드는 c++11부터 나온 키워드. 파생클래스에서 기반클래스의 가상함수를 오버라이드 하는 경우 override 키워드를 통해 명시적으로 나타낼 수 있게 됨.
오버라이드를 하려면 함수형이 완전히 같아야 함. override 키워드를 쓰면 오버라이드가 이뤄진 게 맞는지 더 정확하게 판단할 수 있음.(함수꼴이 달라 오버라이드가 이뤄지지 않는 경우 오류 발생함)
자식 클래스에서 명시적으로 override를 하는 경우 암묵적으로 virtual이라는 가정 하에 작성하기 때문에 virtual 키워드를 사용하지 않음
Q. 그럼 모든 함수를 가상함수로 만들면 안 되나?