💡 동적 바인딩, 가상 소멸자
바인딩(binding) : 함수를 호출할 때 어떤 스코프의 함수인지 메모리를 할당하는 것
virtual
을 사용하면 포인터 타입(Person)에 따른 함수가 아니라, 저장한 객채(sujilee)의 함수를 호출할 수 있는 것을 전 게시글에서 확인하였다. 왜 그렇게 동작할까?
C++의 컴파일러는 함수 호출 시 해당 스코프 안에 있는 함수를 호출해야 하고, 해당 함수가 할당된 메모리 주소도 알아야한다. 그래야 해당 함수가 호출되었을 때 해당 함수의 시작 주소로 점프할 수 있기 때문이다.
정적 바인딩 | 동적 바인딩 |
---|---|
컴파일 시에 필요한 함수를 바인딩. 가상 함수가 아닌 멤버는 모두 정적 바인딩을 하게 된다. | 가상 함수는 런 타임 시에 함수들을 바인딩해 사용한다. |
C++에서는 함수의 오버로딩 기능으로 인해 바인딩 작업이 복잡해진다. 일반적인 멤버 함수의 경우 함수명이 중복되지 않기 때문에 컴파일 시 정적 바인딩을 하면 그만인데, 오버로딩을 위해 가상 함수를 사용하게 되면 컴파일러는 어떤 함수를 호출해야하는지 미리 알 수 없다. 따라서 런 타임(프로그램 실행 중)에 올바른 함수가 실행될 수 있도록 동적 바인딩을 한다.
모든 가상 함수가 동적 바인딩을 하지는 않음
타입이 분명할 때에는 정적 바인딩, 부모클래스 타입 포인터나 참조를 통해 호출할 시는 동적 바인딩
virtual
키워드를 사용해 포인터로 클래스를 이용하는데, 만약 동적 할당을 이요해 할당한 객체를 가리키는 경우도 있을 것이다.
일반적인 상속 시 생성자와 소멸자 호출과정을 살펴보자
#include <iostream>
class Parent {
public:
Parent() {
std::cout << "Parent class Constructor called" << std::endl;
}
~Parent() {
std::cout << "Parent class Destructor called" << std::endl;
}
};
class Child: public Parent {
public:
Child() {
std::cout << "Child class Constructor called" << std::endl;
}
~Child() {
std::cout << "Child class Destructor called" << std::endl;
}
};
int main() {
Child kid;
}
결과
Parent class Constructor called
Child class Constructor called
Child class Destructor called
Parent class Destructor called
자식클래스 객체를 생성하려면 먼저 부모 클래스의 멤버가 전달되어야하므로, 부모 클래스의 생성자가 가장 먼저 호출된다. 그 후 자식클래스의 생성자 호출. 소멸자는 자식클래스의 소멸자가 먼저 호출되고 그 다음 부모클래스의 소멸자가 호출된다.
int main() {
Parent *adult;
Child *kid = new Child;
adult = kid;
delete adult;
}
결과
Parent class Constructor called
Child class Constructor called
Parent class Destructor called
전 게시글에서 우리는 부모 클래스의 포인터로부터 자식클래스를 호출할 경우, 가상 함수로 정의하지 않는 함수를 오버로딩해 사용할 시 부모클래스의 멤버 함수가 호출되는 것을 확인했다.
소멸자 또한 함수이고, 자시클래스에서 오버로딩된 함수라고 볼 수 있기 때문에 소멸자 호출 시 부모클래스의 소멸자만 호출된다. 이 문제를 해결하는 방법이 바로 가상 소멸자를 사용하는 것이다.
#include <iostream>
class Parent {
public:
Parent() {
std::cout << "Parent class Constructor called" << std::endl;
}
virtual ~Parent() {
std::cout << "Parent class Destructor called" << std::endl;
}
};
class Child: public Parent {
public:
Child() {
std::cout << "Child class Constructor called" << std::endl;
}
virtual ~Child() {
std::cout << "Child class Destructor called" << std::endl;
}
};
int main() {
Parent *adult;
Child *kid = new Child;
adult = kid;
delete adult;
}
결과
Parent class Constructor called
Child class Constructor called
Child class Destructor called
Parent class Destructor called
동적 할당을 사용하고자 하면, 반드시 소멸자도 가상 함수로 선언해 자식클래스의 소멸자도 호출될 수 있도록 하자