티스토리 뷰

주의 사항!

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

 

다중 상속을 이해하기 앞서

다중 상속이란, 둘 이상의 클래스를 동시에 상속하는 것을 말합니다. 그리고 C++은 다중 상속을 지원하는 객체지향 언어입니다. 그런데 다중 상속은 제법 논란이 되는 문법입니다.

 

다중 상속에 대한 프로그래머들의 첫 번째 의견은 다음과 같습니다.

"다중 상속은 득 보다 실이 더 많은 문법이다. 그러니 절대로 사용하지 말아야 하며, 가능하다면 C++의 기본 문법에서 제외시켜야 한다."

 

다음은 이보다 조금 더 부드러운 의견입니다.

"일반적인 경우에서 다중 상속은 다양한 문제를 동반한다. 따라서 가급적 사용하지 않아야 함에는 동의를 한다. 그러나 예외적으로 매우 제한적인 사용까지 부정할 필요는 없다고 본다."

 

프로그래머들의 의견을 보면 대부분이 다중 상속에 대해 좋지 않게 생각하고 있음을 확인할 수 있습니다. 실제로 다중 상속으로만 해결할 수 있는 문제는 존재하지 않습니다. 따라서 굳이 다중 상속을 사용하기 위해 노력할 필요는 없습니다.

 

따라서 '다중 상속이라는 것이 있다'는 것과 '어떻게 사용을 할 수 있었다' 정도만 기억하고, 다중 상속을 사용해야겠다는 절실한 필요성을 느낄 때 다시 찾아볼 수 있을 정도를 목표로 다중 상속에 대해 배웁니다.

 

다중 상속의 문법적 이해

다음 예제를 통해서 다중 상속의 방법을 설명하겠습니다.

#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, public BaseTwo
{
public:
	void ComplexFunc()
	{
		SimpleFuncOne();
		SimpleFuncTwo();
	}
};

int main(void)
{
	MultiDerived mdr;
	mdr.ComplexFunc();

	return 0;
}

/*
실행결과

BaseOne
BaseTwo

*/

 

다중 상속은 상속을 두 번 이상 하는 것뿐이므로 방법은 매우 간단합니다.

 

둘 이상의 기초 클래스에 동일한 이름의 멤버가 존재하는 경우

다중 상속의 대상이 되는 두 기초 클래스에 동일한 이름의 멤버가 존재하는 경우에는 문제가 발생할 수 있습니다. 다음 예제를 보겠습니다.

#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, public BaseTwo
{
public:
	void ComplexFunc()
	{
		SimpleFunc();    //컴파일 에러 발생
		SimpleFunc();
	}
};

int main(void)
{
	MultiDerived mdr;
	mdr.ComplexFunc();

	return 0;
}

 

위 예제를 보면 BaseOne 클래스와 BaseTwo 클래스에 같은 이름의 SimpleFunc 함수가 정의되어 있습니다. 그리고 이 두 클래스를 다중 상속받는 MultiDerived 클래스의 ComplexFunc 함수에서 SimpleFunc 함수를 호출하고 있습니다. 이렇게 되면 호출하고자 하는 SimpleFunc 함수가 BaseOne의 것인지 BaseTwo의 것인지 컴파일러는 알 수가 없습니다. 따라서 컴파일 에러를 발생시킵니다.

 

이런 문제를 해결하려면 문제가 되는 코드를 다음과 같이 수정해야 합니다.

void ComplexFunc()
{
	BaseOne::SimpleFunc();    
	BaseTwo::SimpleFunc();
}

 

간접 상속

함수 호출 관계의 모호함은 다른 상황에서도 발생할 수 있습니다. 다음 예제를 살펴보겠습니다.

#include <iostream>
using namespace std;

class Base
{
public:
	Base() { cout << "Base constructor" << endl; }
	void BaseFunc()
	{
		cout << "BaseFunc" << endl;
	}
};

class MiddleDerivedOne : public Base
{
public:
	MiddleDerivedOne() { cout << "MiddleOne constructor" << endl; }
	void MiddleFuncOne()
	{
		BaseFunc();
		cout << "MiddleFuncOne" << endl;
	}
};

class MiddleDerivedTwo : public Base
{
public:
	MiddleDerivedTwo() { cout << "MiddleTwo constructor" << endl; }
	void MiddleFuncTwo()
	{
		BaseFunc();
		cout << "MiddleFuncTwo" << endl;
	}
};

class LastDerived : public MiddleDerivedOne, public MiddleDerivedTwo
{
public:
	LastDerived() { cout << "Last constructor" << endl; }
	void ComplexFunc()
	{
		MiddleFuncOne();
		MiddleFuncTwo();
		BaseFunc();    //컴파일 에러 발생
	}
};

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

	return 0;
}

 

위 예제에서 MiddleDerivedOne 클래스와 MiddleDerivedTwo 클래스는 Base 클래스를 상속받고 있습니다. 그리고 이 두 클래스를 LastDerived 클래스가 다중 상속받고 있습니다.

 

여기서 LastDerived 객체의 구조를 생각해보겠습니다. 우선 LastDerived 클래스의 멤버를 비롯해, 상속받은 MiddleDerivedOne 클래스와 MiddleDerivedTwo 클래스의 멤버까지 가지고 있습니다. 그런데 MiddleDerivedOne 클래스와 MiddleDerivedTwo 클래스는 각각 Base 클래스를 상속받고 있기 때문에, MiddleDerivedOne을 통해 간접 상속받은 Base의 멤버와 MiddleDerivedTwo를 통해 간접 상속받은 Base의 멤버까지도 가지게 됩니다.

 

결국 LastDerived 객체는 Base 클래스의 멤버를 두 개씩 가지고 있게 되는 셈입니다. 따라서 Base 클래스의 멤버 함수인 BaseFunc을 호출하려 하면 둘 중 어느 함수를 호출해야 하는지 컴파일러가 알 수 없어 에러를 발생합니다.

 

이 문제는 다음과 같이 '간접 상속'을 통해서 해결할 수 있습니다.

#include <iostream>
using namespace std;

class Base
{
public:
	Base() { cout << "Base constructor" << endl; }
	void BaseFunc()
	{
		cout << "BaseFunc" << endl;
	}
};

class MiddleDerivedOne : virtual public Base    //간접상속
{
public:
	MiddleDerivedOne() { cout << "MiddleOne constructor" << endl; }
	void MiddleFuncOne()
	{
		BaseFunc();
		cout << "MiddleFuncOne" << endl;
	}
};

class MiddleDerivedTwo : virtual public Base    //간접상속
{
public:
	MiddleDerivedTwo() { cout << "MiddleTwo constructor" << endl; }
	void MiddleFuncTwo()
	{
		BaseFunc();
		cout << "MiddleFuncTwo" << endl;
	}
};

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

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

	return 0;
}

/*
실행결과

객체 생성 전...
Base constructor
MiddleOne constructor
MiddleTwo constructor
Last constructor
객체 생성 후...
BaseFunc
MiddleFuncOne
BaseFunc
MiddleFuncTwo
BaseFunc

*/

 

virtual 키워드를 사용해서 Base를 간접 상속하게 되면 MiddleDerivedOne 클래스와 MiddleDerivedTwo 클래스는 Base 클래스 하나를 서로 공유하게 됩니다. 따라서 LastDerived 클래스에도 Base의 멤버는 하나씩 존재할 수 있게 됩니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
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
글 보관함