virtual 소멸자 (가상 소멸자)
가상 함수 테이블 (virtual function table)
다중 상속
가상 상속
6-2 정리
Parent* p = new Parent();
Parent* c = new Child();
- 컴퓨터 입장에서 p , c 모두 Parent 를 가리키는 포인터들이므로 당연히
```c
p->f(); --1
c->f(); --2
#include <iostream>
class Parent
{
public:
Parent() {std::cout << " Parent 생성자 호출 " << std::endl;}
~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;
{ Child c; }
std::cout << " ---Parent 포인터로 Child 가리켰을때 ---" << std::endl;
{
Parent *p = new Child();
delete p;
}
}
>>
---평범한 Child 만들었을때 ---
Parent 생성자 호출
Child 생성자 호출
child 소멸자 호출
Parent 소멸자 호출
---Parent 포인터로 Child 가리켰을때 ---
Parent 생성자 호출
Child 생성자 호출
Parent 소멸자 호출
std::cout << " ---평범한 Child 만들었을때 --- " <<std::endl;
{ Child c; }
std::cout << " ---Parent 포인터로 Child 가리켰을때 ---" << std::endl;
{
Parent *p = new Child();
delete p;
}
delete p 를 해도 , p 가 가리키는것은 부모가 아니라 자식 객체 이기 때문에
보통의 Child 객체가 소멸되는 것과 같은 순서로 소멸자가 호출 되어야 하는데
자식 소멸자가 호출 되지 않는다.
이럴 경우 메모리 누수 가 생긴다.
하지만 소멸자를 virtual 로 만들면 p 가 소멸자 호출할때 가리키는게 객체가 뭔지 한번더 체크한다.
class Parent
{
public:
Parent() {std::cout << " Parent 생성자 호출 " << std::endl;}
virtual ~Parent() { std::cout << " Parent 소멸자 호출 " << std::endl;}
};
>>
---평범한 Child 만들었을때 ---
Parent 생성자 호출
Child 생성자 호출
child 소멸자 호출
Parent 소멸자 호출
---Parent 포인터로 Child 가리켰을때 ---
Parent 생성자 호출
Child 생성자 호출
child 소멸자 호출
Parent 소멸자 호출
제대로 자식 소멸자를 찾아서 호출됨을 알수있다.
Q: 왜 Parent 소멸자는 호출이 되었는가?
반면 부모 소멸자를 먼저 호출하면 , 부모는 자식객체가 있는지 없는지 모르므로 자식 소멸자를 호출할수 없다.
따라서 상속될 여지가 있는 Base 클래스들은 반드시 소멸자를 virtual로 만들어 주어야 나중에 문제가 발생할 여지가 없다.
#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;
}
>>
Parent !
Child !
override 는 컴파일시 -std=c++11
void test(A& a)
{
a.show();
}
test(b)
를 통해 B클래스의 객체를 전달 해도 B클래스의 함수가 잘 호출 되는것을 알수 있다.
이는 B클래스가 A클래스를 상속 받고 있기 때문이다.
즉 함수에 타입이 기반 클래스여도 그 파생 클래스는 타입 변환되어 전달 할 수있다.
따라서 test함수에서 show() 호출 할때 인자로 b를 전달했다면
비록 전달된 인자가 A의 객체라고 표현 되었지만
show 함수가 virtual 로 정의 되어 있기 때문에
알아서 B의 show를 찾아서 호출한다.
Q: 그냥 그럼 모든 함수를 virtual 로 만들면 안 되나?
Q: 그럼 왜 C++에서 virtual 키워드를 이용해 사용자가 직접 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)을 만들게 된다(전화번호부)
함수 이름(가게명)과 실제로 어떤함수(그 가게 전화번호) 가 대응 되는지 테이블로 저장하고있다
위의 경우 Parent 와 Child 모두 가상함수를 포함하고 있기 때문에 아래와 같이 구성된다
가상함수와 비 가상함수의 차이는
Child 의 func3() 와 같이 그냥 vtable을 거치지 않고 , 바로 func3()을 호출하면 직접 실행 된다.
하지만 가상함수를 호출할땐
가상테이블을 한단계 더 거쳐서 실제로 어떤 함수를 고를지 결정한다.
예를 들어
Parent* p = Parent();
p->func1();
컴파일러는
Parent *c = Child();
c->func1();
이와 같이 두 단계에 걸쳐서 함수를 호출해서 동적바인딩을 구현 할 수 있게 된다.
#include <iostream>
class Animal
{
public:
Animal() {}
virtual ~Animal() {}
virtual void speak() = 0;
};
class Dog : public Animal
{
public:
Dog() : Animal() {}
void speak() {std::cout << "wall wall" << std::endl;}
};
class Cat : public Animal
{
public:
Cat(): Animal() {}
void speak() { std::cout << "mewow mewow" <<std::endl;}
};
int main()
{
Animal* dog = new Dog();
Animal* cat = new Cat();
Dog d1;
d1.speak();
dog->speak();
cat->speak();
}
여기서
virtual void speak() = 0;
그런데
Animal a;
a.speak();
에러 난다
아예 Animal객체 생성 못하게 컴파일러가 막는다.
따라서 Animal처럼 순수 가상함수를 최소 한개 이상 포함하고 있는 클래스는
객체를 생성할 수 없다.
인스턴스화 시키기 위해서는 이 클래스를 상속 받는 클래스를 만들어서 모든 순수 가상함수를 오버라이딩 해주어야만한다.
이렇게 순수가상 함수를 최소 한개 포함하고 있는
즉 반드시 상속 되어야 하는 클래스를 가리켜
추상 클래스 라고 부른다.
- 참고로 private 안에 순수 가상함수를 정의 해도 문제될것은 없다.
private에 정의 되어 있다고 해서 오버라이드 안 된다는 뜻은 아니기 때문
근데 자식 클래스 에서 호출은 못함.
Q: 추상 클래스 는 왜 사용해?
A:
추상클래스의 또 다른 특징
Animal* dog = new Dog();
Animal* cat = new Cat();
dog->speak();
cat->speak();
class Human {
// ...
};
class HandsomeHuman : public Human {
// ...
};
class SmartHuman : public Human {
// ...
};
class Me : public HandsomeHuman, public SmartHuman {
// ...
};
해결 방법은
class Human {
public:
// ...
};
class HandsomeHuman : public virtual Human {
// ...
};
class SmartHuman : public virtual Human {
// ...
};
class Me : public HandsomeHuman, public SmartHuman {
// ...
};