티스토리 뷰

주의 사항!

  • 이 글은 제가 직접 공부하는 중에 작성되고 있습니다.
  • 따라서 제가 이해하는 그대로의 내용이 포함됩니다.
  • 따라서 이 글은 사실과는 다른 내용이 포함될 수 있습니다.


지금까지는 객체 내에 멤버 함수가 존재한다고 설명해 왔습니다. 하지만 이는 사실이 아닙니다.

 

멤버 함수의 동작 원리

지금부터 구조체 변수와 전역 함수를 이용해서 클래스와 객체를 흉내 내보겠습니다. 이는 객체의 멤버 변수가 어떠한 형태로 구성되는지를 설명하기 위함입니다. 그럼 먼저 흉내 낼 모델이 되는 C++ 코드입니다.

#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;
}

/*
실행결과

Data : 32

*/

 

정수 데이터를 처리할 Data 클래스를 정의했습니다. 해당 클래스는 데이터를 출력하는 ShowData와 데이터를 더하는 Add를 멤버 함수로 가지고 있습니다. 이어서 위 코드를 C언어로, 구조체와 전역 함수를 사용해 흉내 낸 코드입니다.

#include <stdio.h>

struct Data;

struct Data
{
	int data;
	void (*ShowData)(Data*);
	void (*Add)(Data*, int);
};

typedef struct Data Data;

void ShowData(Data* THIS)
{
	printf("Data : %d\n", THIS->data);
}

void Add(Data* THIS, int num)
{
	THIS->data += num;
}

int main(void)
{
	Data obj1 = { 15, ShowData, Add };
	obj1.Add(&obj1, 17);
	obj1.ShowData(&obj1);

	Data obj2 = { 7, ShowData, Add };
	obj1.Add(&obj2, 8);
	obj1.ShowData(&obj2);

	return 0;
}

/*
실행결과

Data : 32
Data : 15

*/

 

위 코드를 살펴보겠습니다. 구조체 Data를 선언해 주었습니다. 그런데 안에 멤버는 객체 Data와는 조금 다릅니다. data 변수가 선언되어 있는 것까지는 같으나 이후 ShowData 함수 포인터와 Add 함수 포인터가 선언되어 있습니다. 그리고 이 두 함수는 구조체 밖에 전역 함수로 선언되고 정의되었습니다.

 

main함수를 보면 Data 구조체 변수 obj1을 선언하는데 초기화 값으로 주어지는 인자 중에 ShowData 함수와 Add함수가 포함되어 있습니다. 두 번째로 선언하는 구조체 변수 obj2에도 역시 같은 함수를 인자로 주고 있습니다.

 

이로써 유추해 볼 수 있는 멤버 함수의 특성은 다음과 같습니다.

  • 멤버 함수는 객체 안에 존재하지 않고 객체 밖의 별도의 메모리 공간에 존재합니다.
  • 그리고 객체들은 이 멤버 함수를 공유하고 있습니다.
  • 즉, 생성되는 객체마다 멤버 함수를 하나씩 가지고 생성되는 것이 아니라, 객체가 생성되면 이미 어느 메모리 공간에 정의되어 있던 이 함수를 공유하게 되는 것입니다.

 

가상 함수의 동작 원리

이어서 가상 함수의 동작 원리에 대해 설명합니다. 먼저 다음의 예를 살펴봅니다.

#include <iostream>
using namespace std;

class AAA
{
public:
	virtual void Func1()
	{
		cout << "AAA::Func1" << endl;
	}

	void Func2()
	{
		cout << "AAA::Func2" << endl;
	}
};

class BBB : public AAA
{
public:
	 void Func1()
	{
		cout << "BBB::Func1" << endl;
	}

	void Func3()
	{
		cout << "BBB::Func3" << endl;
	}
};

int main(void)
{
	AAA* aptr = new AAA;
	aptr->Func1();

	AAA* bptr = new BBB;
	bptr->Func1();
}

/*
실행결과

AAA::Func1
BBB::Func1

*/

 

위의 코드를 살펴보면 AAA 클래스가 선언되었고 가상 함수 Fun1과 멤버 함수 Fun2를 가지고 있습니다. 그리고 이어서 BBB 클래스가 선언되었고, AAA 클래스를 상속받고 있으며, AAA에 있는 것과 같은 가상 함수 Fun1과 멤버 함수 Fun3를 가지고 있습니다.

 

이처럼 가상 함수를 하나 이상 가지고 있는 클래스에 대해서는 컴파일러가 다음과 같은 형태의 '가상 함수 테이블'을 만듭니다. 해당 테이블에는 가상 함수뿐만 아니라 일반 멤버 함수도 저장됩니다.

  • AAA클래스의 가상 함수 테이블
key value
void AAA::Func1 0x1024번지
void AAA::Func2 0x2048번지

위의 가상 함수 테이블을 보면 key가 있고 value가 있습니다. key는 호출하고자 하는 함수를 구분 지어주는 구분자의 역할을 합니다. 그리고 value는 해당 함수의 주소 정보를 알려주는 역할을 합니다.

 

이어서 BBB클래스의 가상 함수 테이블을 보겠습니다.

  • BBB클래스의 가상 함수 테이블
key value
void BBB::Func1 0x3072번지
void AAA::Func2 0x2048번지
void BBB::Func3 0x4096번지

BBB클래스는 AAA클래스를 상속받고 있습니다. 따라서 AAA클래스의 멤버 함수들까지 BBB클래스의 가상 함수 테이블에 포함됩니다. 그런데 이상한 점이 있습니다. AAA클래스의 멤버 함수는 총 2개, BBB 클래스의 멤버 함수도 2개이므로 BBB 클래스의 가상 함수 테이블에는 총 4개의 함수가 표현되어 있어야 합니다. 그런데 AAA 클래스의 오버 라이딩된 가상 함수 Func1에 대한 정보가 존재하지 않습니다. 이렇듯 오버 라이딩된 가상 함수의 주소 정보는 유도 클래스의 가상 함수 테이블에 포함되지 않습니다. 때문에 오버 라이딩된 가상 함수를 호출하면 무조건 가장 마지막에 오버 라이딩한 유도 클래스의 멤버 함수가 호출됩니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함