오버라이딩, 가상함수
오버라이드
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() : s("파생"), Base() {
std::cout << "파생 클래스" << std::enld;
}
void what() {
std::cout << s << std::enld;
}
int main() {
std::cout << " === 기반 클래스 생성 === " << std::endl;
Base p;
p.what();
std::cout << " === 파생 클래스 생성 === " << std::endl;
Derived c;
c.what();
return 0;
}
p.what() => Base의 what()을 호출, "기반" 출력
c.what() => Base를 상속받는 Derived의 what() 호출, Derived의 what()이 Base의 what()을
오버라이드해서 Derived의 what()이 호출, "파생" 출력
업 캐스팅
int main() {
Base p;
Derived c;
std::cout << "=== 포인터 버전 ===" << std::endl;
Base* p_c = &c; // Derived의 객체 c를 Base 객체를 가리키는 포인터에 넣음
p_c->what();
return 0;
}
Derived가 Base를 상속받았으므로 Derived의 객체 c는 Base의 객체이기도 하다.
따라서 Base* p_c = &c와 같이 Base 객체가 가리키는 포인터가 c를 가리키는 것이 가능하다.
하지만 p는 Base 객체를 가리키는 포인터이므로 p_c -> what()을 호출하면 p는 Base의 what()을
실행하고, Base의 s를 출력하게 된다.
이렇게 파생클래스에서 기반 클래스로 캐스팅 하는 것을 업 캐스팅이라 한다.
다운 캐스팅
int main() {
Base p;
Derived c;
std::cout << " === 포인터 버전 === " << std::endl;
Derived* p_p = &p;
p_p->what();
return 0;
}
Derived의 포인터 p_p가 Base 객체인 p를 가리킨다. 그리고 Derived의 포인터 p_p가 what()을 호출하면
Derived의 what()이 호출되어야 한다. 하지만 이는 불가능하다.
Derived 포인터는 p_p를 가리키고 p_p가 가리키는 객체는 Base 객체인데 기반 클래스인 Base에는 파생 클래스인 Derived의 정보가 없기 때문이다.
즉, p_p는 Derived객체이고, 가리키는 객체 또한 Derived라고 생각해 Derived의 what()을 호출하려 하지만
실제로는 Base 객체를 가리키고 있고 Base 객체에는 Derived에 대한 정보가 없기 때문에 호출이 불가능한 것이다.
이와 같은 문제를 막기 위해 컴파일러 상에서 함부로 다운 캐스팅 하는 것을 금지하고 있다.
강제 형변환
int main() {
Base p;
Derived c;
std::cout << " === 포인터 버전 === " << std::endl;
Base* p_p = &c;
Derived* p_c = p_p;
p_c -> what();
return 0;
}
위의 다운 캐스팅 문제와 같이 Derived* p_c에 Base*를 대입하면 안된다.
하지만 p_p는 Derived 객체를 가리키고 있다는것을 우리는 알 수 있으므로
(Derived* 객체가 Base* 포인터를 다운 캐스팅함에도 p_p는 c를 가리키므로 사실상 p_c는 Derived를 가리키는 것이다.)
따라서
Derived* p_c = static_cast<Derived*> (p_p); 와 같이 강제 형변환을 하여 컴파일이 가능하다.
만일 p_p가 Derived가 아닌 Base객체를 가리키는데 강제 형변환을 통해 컴파일을 한다면 런타임 오류가
발생한다.
따라서 다운 캐스팅은 작동이 보장되지 않는 한 매우 권장하지 않는 방법이다.
파생 클래스에서 기반 클래스의 가상 함수를 오버라이드 하는 경우, override 키워드를 통해
명시적으로 나타낼 수 있다.
ex)
void what() override { std::cout << s << std::endl; }
Derived 클래스의 what() 함수는 Base 클래스의 what()함수를 오버라이드 하므로
override 키워드를 통해 이를 알려주고 있다.
override 키워드를 사용하면 실수로 오버라이드 하지 않는 경우를 막을 수 있다.
가상함수 (Virtual Function)
virtual 키워드로 선언된 멤버 함수
동적 바인딩 지시어, 컴파일러에게 함수에 대한 호출 바인딩을 실행 시간까지 미루도록 지시
순수 가상 함수
파생 클래스에서 재정의할 함수를 알려주는 역할 (실행할 코드를 작성할 목적X)
멤버 함수의 원형 = 0
ex)
virtual void draw() = 0;
추상 클래스
최소한 하나의 순수 가상 함수를 가진 클래스
온전한 클래스가 아니므로 객체 생성 불가능
추상 클래스의 포인터는 선언 가능
추상 클래스의 인스턴스를 생성할 목적이 아님, 상속에서 기본 클래스의 역할을 하기 위한 목적
(순수 가상 함수를 통해 파생 클래스에서 구현할 함수의 원형을 보여주는 인터페이스 역할)
추상 클래스의 모든 멤버 함수를 순수 가상 함수로 선언할 필요는 없음
동적 바인딩
파생 클래스에 대해 기반 클래스에 대한 포인터로 가상 함수를 호출하는 경우 객체 내에
오버라이딩한 파생 클래스의 함수를 찾아 실행
실행 중에 이루어짐
'c++' 카테고리의 다른 글
함수 템플릿 (0) | 2020.08.27 |
---|---|
템플릿 (Template) (0) | 2020.08.27 |
String class (0) | 2020.08.18 |
explicit, mutable (0) | 2020.08.18 |
초기화 리스트 (Initializer list) (0) | 2020.08.17 |