[C++] Chapter 09 - 가상(Virtual)의 원리와 다중상속

Lee Jeong Min·2021년 1월 11일
0

Cpp

목록 보기
9/16
post-thumbnail

09-1 멤버함수와 가상함수의 동작원리

객체 안에 정말로 멤버함수가 존재하는가?

이 물음에 답하기 위해 C언어 스타일로 Data클래스를 정의해서 설명함.
RealObjUnder2.cpp

#include <iostream>
using namespace std;

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

이 예제에서 볼 수 있는 것은 두 개의 구조체 변수가 함수를 공유하고 있다는 점이다. 실제로 C++의 객체와 멤버함수는 이러한 관계를 갖고있음(멤버함수는 메모리의 한 공간에 별도로 위치하고선, 이 함수가 정의된 클래스의 모든 객체가 이를 공유하는 형태)

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

가상함수의 원리를 설명하기 위한 예제
VirutalInteral.cpp

#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와 BBB에 대한 가상 테이블을 만들게 되는데 여기에선 Func1에 대해 오버라이딩을 하고 있기 때문에 BBB의 가상테이블엔 AAA::Func1 에 대한 정보가 존재하지 않음. --> 그래서 오버라이딩 된 가상 함수를 호출 시 항상 마지막에 오버라이딩을 한 유도 클래스의 멤버함수가 호출되는것!


09-2 다중상속(Multiple Inheritance)에 대한 이해

다중상속의 기본방법

MultiInheri1.cpp

#include <iostream>
using namespace std;

class BaseOne
{
public:
	void SimpleFuncOne() { cout << "BaseOne" << endl; }
};

class BaseTwo
{
public:
	void SimpleFuncTwo() { cout << "BaseTwo" << endl; }
};

class MultiDerived : public BaseOne, protected BaseTwo
{
public:
	void CompleFunc()
	{
		SimpleFuncOne();
		SimpleFuncTwo();
	}
};

int main(void)
{
	MultiDerived mdr;
	mdr.CompleFunc();
	return 0;
}

다중상속의 모호성

다중상속의 대상이 되는 두 기초 클래스에 동일한 이름의 멤버가 존재할 때 어느 클래스에 선언된 멤버에 접근을 하려는 것인 가에 관한 문제

관련 예제
MultiInheri2.cpp

#include <iostream>
using namespace std;

class BaseOne
{
public:
	void SimpleFunc() { cout << "BaseOne" << endl; }
};

class BaseTwo
{
public:
	void SimpleFunc() { cout << "BaseTwo" << endl; }
};

class MultiDerived : public BaseOne, protected BaseTwo
{
public:
	void ComplexFunc()
	{
		BaseOne::SimpleFunc();
		BaseTwo::SimpleFunc();
	}
};

int main(void)
{
	MultiDerived mdr;
	mdr.ComplexFunc();
	return 0;
}

가상함수를 사용하지 않고 문제를 해결하려면 어느 클래스에 정의된 함수의 호출을 원하는지 명시해야한다.

가상상속(Virutal Inheritance)

관련예제: MultiInheri3.cpp

#include <iostream>
using namespace std;

class Base
{
public:
	Base() { cout << "Base Constructor" << endl; }
	void SimpleFunc() { cout << "BaseOne" << endl; }
};

class MiddleDerivedOne : virtual public Base
{
public:
	MiddleDerivedOne() : Base()
	{
		cout << "MiddleDerivedOne Constructor" << endl;
	}
	void MiddleFuncOne()
	{
		SimpleFunc();
		cout << "MiddleDerivedOne" << endl;
	}
};

class MiddleDerivedTwo : virtual public Base
{
public:
	MiddleDerivedTwo() : Base()
	{
		cout << "MiddleDerivedTwo Constructor" << endl;
	}
	void MiddleFuncTwo()
	{
		SimpleFunc();
		cout << "MiddleDerivedTwo" << endl;
	}
};

class LastDerived : public MiddleDerivedOne, public MiddleDerivedTwo
{
public:
	LastDerived() : MiddleDerivedOne(), MiddleDerivedTwo()
	{
		cout << "LastDerived Constructor" << endl;
	}
	void ComplexFunc()
	{
		MiddleFuncOne();
		MiddleFuncTwo();
		SimpleFunc();
	}
};

int main(void)
{
	cout << "객체생성 전 ..... " << endl;
	LastDerived ldr;
	cout << "객체생성 후 ....." << endl;
	ldr.ComplexFunc();
	return 0;
}

이 예제에서 중요한점은 Base의 유도 클래스 MiddleDerivedOne과 MiddleDerivedTwo 선언 시 base class 를 가상 상속을 하였다는 점이다. 이로 인해 main함수에서 LastDerived 객체를 선언하고 나서 ComplexFunc()함수 실행 시 바로 SimpleFunc()함수를 사용할 수 있게 되었다. 만약 가상함수를 사용하지 않았다면 Base클래스의 생성자는 두번 호출되고, SimpleFunc()함수 실행시 어떤 클래스의 함수를 사용할 것인지 앞에 명시를 해주어야 한다.

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글