가상함수에 대해서
- 가상 함수는 기본 클래스에서 선언된 함수로, 파생클래스에서 이 함수를 재정의(override)할 수 있게 합니다.
어떨 때 쓰이는가?
- 다형성을 구현하기 위해 사용되며, 포인터나 참조를 통해 기본 클래스 형식으로 호출된 함수가 실제 객체의 타입에 따라 적절한 파생 클래스의 함수를 호출할 수 있게 합니다.
가상 함수의 기본 구문
- 가상 함수를 선언하려면 기본 클래스에서 함수 선언 앞에 'virtual' 키워드를 사용합니다.
- 파생 클래스에서 동일한 함수 시그니처로 함수를 재정의하면, 이 함수는 가상 함수로 동작합니다.
class Base {
public:
virtual void show() {
std::cout << "Base class show function" << std::endl;
}
};
class Derived : public Base {
public:
virtual void show() override {
std::cout << "Derived class show function" << std::endl;
}
};
동작 원리
가상 함수 테이블
- 가상 함수는 가상함수 테이블을 통해 구현됩니다. 가상함수 테이블은 가상 함수 포인터의 배열로, 각 객체의 가상 함수 포인터가 이 테이블을 가리킵니다.
- 각 클래스는 자신의 가상함수 테이블을 가지고 있으며, 가상 함수 호출 시 가상함수 테이블을 통해 적절한 함수가 호출됩니다.
- 컴파일러는 클래스가 가상 함수를 포함하고 있으면 해당 클래스에 대한 가상 함수 테이블을 생성합니다.
- 가상 함수를 호출할 때, 객체의 가상 함수 포인터를 통해 가상 함수 테이블을 참조합니다.
실행 시간 결정
- 가상 함수 호출은 실행 시간(런타임 시간)에 결정됩니다. 객체의 실제 타입에 따라 적절한 함수가 호출됩니다.
- 런타임 시간에 호출을 결정하는 이것을 동적 바인딩이라고 합니다.
- 가상 함수 테이블(V-Table):
- 클래스당 하나씩 생성됩니다.
- 클래스의 모든 객체가 동일한 V-Table을 공유합니다.
- V-Table은 클래스의 가상 함수 주소들을 저장합니다.
- 가상 함수 포인터(V-Ptr):
- 각 객체마다 하나씩 존재합니다.
- 각 객체의 V-Ptr은 해당 객체의 클래스의 V-Table을 가리킵니다.
- V-Ptr은 객체의 메모리 내에 저장되며, 64비트 시스템에서는 8바이트를 차지합니다.
- 가상 함수를 많이 선언해도 각 객체는 V-Ptr 하나만을 가지므로 추가적인 메모리 소모는 8바이트로 동일합니다.
Base* b;
Derived d;
b = &d;
b->show(); // "Derived class show function" 출력
위의 코드는 런타임에 객체의 실제 타입이 지정되어 파생클래스의 함수가 호출되는 것을 보여줍니다.
가상 소멸자
- 기본 클래스의 소멸자가 가상 함수가 아니면, 파생 클래스의 소멸자가 호출되지 않을 수 있습니다. 이는 메모리 누수를 일으킬 수 있습니다. 따라서 상속 계층에서 동적 메모리 할당을 사용하는 경우, 기본 클래스의 소멸자를 가상으로 선언해야 합니다.
class Base {
public:
virtual ~Base() {
std::cout << "Base destructor" << std::endl;
}
};
class Derived : public Base {
public:
~Derived() {
std::cout << "Derived destructor" << std::endl;
}
};
Base* b = new Derived();
delete b; // "Derived destructor"와 "Base destructor"가 모두 호출됨
위의 코드는 파생클래스의 객체를 가지고 있는 기본 클래스의 가상 소멸자 호출을 보여줍니다.
순수 가상 함수와 추상 클래스
- 순수 가상함수는 함수 선언에 ' = 0 ' 을 사용해서 선언됩니다. 이는 해당 함수가 구현되지 않았음을 의미하며, 파생클래스에서 반드시 재정의해야 합니다.
class Abstract {
public:
virtual void show() = 0; // 순수 가상 함수
};
class Concrete : public Abstract {
public:
void show() override {
std::cout << "Concrete class show function" << std::endl;
}
};
- 순수 가상 함수를 하나 이상 포함하는 클래스는 추상 클래스가 되며, 이 클래스는 인스턴스화할 수 없습니다.
- 추상 크래스는 구체적인 구현을 가지지 않는 인터페이스 역할을 하며, 이를 상속받는 파생 클래스에서 구체적인 구현을 제공해야 합니다.
- 인스턴스화란 클래스를 기반으로 객체를 생성하는 것을 말합니다. 추상 클래스는 순수 가상 함수를 포함하고 있어, 이 함수의 구현이 없기 때문에 객체를 생성할 수 없습니다. 만약 추상 클래스를 인스턴스화 하려고 하면 컴파일 에러가 발생합니다.
// 추상 클래스는 인스턴스화할 수 없음
Abstract a; // 컴파일 에러
// 파생 클래스를 인스턴스화
Concrete c;
// 추상 클래스의 포인터로 파생 클래스 객체를 가리킬 수 있음
Abstract* aPtr = &c;
aPtr->pureVirtualFunction(); // "Concrete implementation of pureVirtualFunction" 출력
'C++ > Basic' 카테고리의 다른 글
[C++] 스마트 포인터 (0) | 2024.06.09 |
---|---|
함수 오버로딩 (0) | 2021.09.17 |
vector와 list의 차이점 (0) | 2021.09.16 |
[C++] new와 malloc의 차이 (0) | 2021.09.15 |
[C++] 가변인자 템플릿 (0) | 2021.08.25 |