본문 바로가기

Programming/C++ 3

[C++] 09-01. 멤버함수와 가상함수의 동작 원리

반응형

멤버함수의 동작

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

//적절히 변경된 main 함수
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;
}

 

 

 

 

	void (*ShowData)(Data*);
	void(*Add)(Data*, int);

다수의 객체가 멤버함수를 공유하는 형태로

함수호출시 객체의 정보가 전달되어, 이를 기반으로 함수가 실행되기 때문에 논리적으로는 객체안에 멤버함수가 존재한다.

	obj1.Add(&obj1, 17);
	obj2.Add(&obj2, 9);
void ShowData(Data* THIS)
{
	cout << "Data :" << THIS->data << endl;
}

함수포인터를 이용한 호출{자기자신의 주소값 전달, 데이터 전달}

-> obj1을 대상으로 연산 THIS은 obj1의 주소값

-> obj2를 대상으로 연산

객체의 주소값 변수를 대상으로 연산하기 때문에 하나의 함수내에서 각각의 멤버변수에 접근가능

 

 

C++의 객체는 함수의 주소값을 객체안에 저장해, 하나의 함수를 공유하는 형태로

멤버 함수를 호출할 경우, 각각의 멤버변수에 적용되는 이유이다.

 


가상함수의 동작원리와 가상함수 테이블

멤버함수를 호출할 때에는 객체안에 멤버함수를 저장하지 않고 주소값을 이용해 함수 호출을 명령한다.

가상함수를 호출할 때에는

: 컴파일러에 AAA클래스가 바이너리 공간에 올라갈때 AAA클래스의 가상함수 테이블을 메모리 공간에 생성

 

가상함수 테이블은 가상함수의 위치정보를 주소값으로 저장하는 테이블

즉, AAA클래스에는 V-Table정보만 저장한다.

 

AAA클래스의 가상함수 테이블

virtual void Func1() 0x1024번지
virtual void Func2() 0x2048번지

 

BBB클래스의 가상함수 테이블

virtual void BBB::Func1() 0x3072번지
virtual void AAA::Func2() 0x2048번지
virtual void BBB::Func3() 0x4096번지

AAA:Func1()은 존재하지 않는다. -> 가상함수를 이용하면 마지막으로 오버라이딩한 Func1()를 호출하는 이유

 

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

 

 


aptr=bptr;

B클래스가 A클래스를 상속하므로 B클래스의 포인터가 가리키는 대상은  AAA클래스의 포인터가 가리키는 대상을 모두 포함한다.

 

aptr->Func1();

마지막으로 오버라이딩한 Func1()을 호출한다.

-> aptr은 BBB객체를 가리키므로, BBB객체가 가지고있는 V-Table 정보는 void BBB::Func1()만 가지고 있기 때문이다.

반응형