객체 안에 정말로 멤버함수가 존재하는가?
구조체 변수와 전역함수를 이용해서 클래스와 객체를 흉내내보겠다.
#include <iostream>
using namespace std;
class Data {
private:
int data;
public:
Data(int num) : data(num)
{ }
void ShowData() { cout << "Data: " << data << endl; }
void Add(int num) { data += num; }
};
int main(void) {
Data obj(15);
obj.Add(17);
obj.ShowData();
return 0;
}
#include<iostream>
using namespace std;
//클래스 Data를 흉내 낸 영역
typedef struct Data {
int data;
void (*ShowData)(Data*);
void (*Add)(Data*, int);
} Data;
void ShowData(Data* THIS ) { cout << "Data: " << THIS->data << endl; }
void Add(Data* THIS, int num) { THIS->data += num; }
int main(void) {
Data obj1 = { 15,ShowData, Add };
Data obj2 = { 7, ShowData, Add };
obj1.Add(&obj1, 17);
obj2.Add(&obj2, 9);
obj1.ShowData(&obj1);
obj2.ShowData(&obj2);
return 0;
}
위의 예제에서 두 개의 구조체 변수(객체) obj1,obj2가 ShowData,Add 함수를 공유하고 있다.
실제로 C++의 객체와 멤버함수도 이러한 관계를 갖는다.
즉, 객체가 생성되면 멤버변수는 객체 내에 존재하지만, 멤버함수는 메모리의 한 공간에 별도로 위치하고,
이 함수가 정의된 클래스의 모든 객체가 이를 공유하는 형태를 취한다.
가상함수의 동작원리와 가상함수 테이블
#include <iostream>
using namespace std;
class AAA {
private:
int num1;
public:
virtual void Func1() { cout << "Func1" << endl; }
virtual void Func2() { cout << "Func2" << endl; }
};
class BBB : public AAA
{
private:
int num2;
public:
virtual void Func1() { cout << "BBB::Func1" << endl; }
void Func3() { cout << "Func3" << endl; }
};
int main(void) {
AAA* aptr = new AAA();
aptr->Func1();
BBB* bptr = new BBB();
bptr->Func1();
return 0;
}
위 예제의 AAA클래스에서처럼 한 개 이상의 가상함수를 포함하는 클래스에 대해서는 컴파일러가 '가상함수 테이블'이란 것을 만든다.
이를 간단히 'V-Table(Virtual Table)'이라고도 하는데, 이는 실제 호출되어야 할 함수의 위치 정보를 담고있는 테이블이다.
key | value |
void AAA::Func1() | 0x1024번지 |
void AAA::Func2() | 0x2048번지 |
위의 가상함수 테이블을 보면, key와 value가 있다. key는 호출하고자 하는 함수를 구분지어주는 구분자 역할을 한다.
value는 구분자에 해당하는 함수의 주소정보를 알려주는 역할을 한다.
AAA객체의 Func1 함수를 호출해야 할 경우, 위의 테이블에 첫 번째 행의 정보를 참조하여,
0x1024번지에 등록되어있는 Func1함수를 호출하게 되는것이다.
key | value |
void BBB::Func1() | 0x3072번지 |
void AAA::Func2() | 0x2048번지 |
void BBB::Func3() | 0x4096번지 |
BBB클래스도 한 개 이상의 가상함수를 포함하고 있으므로 위와 같은 가상함수 테이블이 생성되며,
다음과 같은 사실을 알 수 있다.
"AAA 클래스의 오버라이딩 된 가상함수 Func1에 대한 정보가 존재하지 않는다."
오버라이딩 된 가상함수의 주소정보는 유도 클래스의 가상함수 테이블에 포함되지 않는다.
따라서 오버라이딩 된 가상함수를 호출하면, 무조건 가장 마지막에 오버라이딩을 한 유도 클래스의 멤버함수가 호출되는 것이다.
가상함수 테이블이 참조되는 방식
앞의 가상함수 예제가 실행되면, main 함수가 호출되기 이전에 가상함수 테이블이 메모리 공간에 할당된다.
참고로 가상함수 테이블은 객체의 생성과 상관없이 메모리 공간에 할당된다.
가상함수 테이블이 멤버함수의 호출에 사용되는 일종의 데이터이기 때문이다.
main함수가 호출되어 객체가 생성되고 나면, AAA 객체에는 AAA 클래스의 가상함수 테이블의 주소 값이 저장되고,
BBB 객체에는 BBB 클래스의 가상함수 테이블의 주소 값이 저장된다.
즉, 가상함수를 하나 이상 멤버로 지니는 클래스의 객체에는 가상함수 테이블의 주소 값이 저장된다.
이 주소값은 우리가 직접 참조할 수는 없다.
만약 AAA 객체를 통해서 Func1함수가 호출된다면,
Func1함수의 위치 확인을 위해서 AAA 클래스의 가상 함수 테이블이 참조되고,
결국 0x1024번지에 위치한 함수가 실행된다.
BBB 클래스의 가상함수 테이블을 살펴보면, 오버라이딩 된 AAA 클래스의 Func1함수에 대한 정보가 없음을 알 수 있다.
그래서 BBB 클래스의 Func1 함수가 대신 호출된다.
이것이 바로 가상함수의 호출 원리이다.
(이 과정에서 실행 속도가 미미하게 감소된다.)
'공부 > C++' 카테고리의 다른 글
[C++] 10-1. 연산자 오버로딩의 이해와 유형 (0) | 2024.11.25 |
---|---|
[C++] 9-2. 다중상속(Multiple Inheritance)에 대한 이해 (0) | 2024.11.22 |
[C++] 08-4. OOP 단계별 프로젝트 06단계 (0) | 2024.11.18 |
[C++] 8-3.가상 소멸자와 참조자의 참조 가능성 (0) | 2024.11.15 |
[C++] 8-2. 가상함수(Virtual Function) (1) | 2024.11.12 |