KoreanFoodie's Study

Effective C++ | 항목 40 : 다중 상속은 심사숙고해서 사용하자 본문

Tutorials/C++ : Advanced

Effective C++ | 항목 40 : 다중 상속은 심사숙고해서 사용하자

GoldGiver 2022. 10. 25. 16:30

C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!

항목 40 : 다중 상속은 심사숙고해서 사용하자

핵심 :

1. 다중 상속은 단일 상속보다 복잡하다. 새로운 모호성 문제를 일으킬 뿐 아니라 가상 상속이 필요해질 수도 있다.
2. 가상 상속을 쓰면 크기 비용, 속도 비용이 늘어나며 초기화 및 대입 연산의 복잡도가 커진다. 따라서 가상 기본 클래스에는 데이터를 두지 않는 것이 현실적으로 가장 실용적이다.
3. 다중 상속을 적법하게 쓸 수 있는 경우가 있다. 여러 시나리오 중 하나는, 인터페이스 클래스로부터 public 상속을 시킴과 동시에 구현을 돕는 클래스로부터 private 상속을 시키는 것이다.


다중 상속으로 인해 생기는 모호성 문제의 예시 코드를 한 번 보자.

class Item
{
public:
	void checkOut() {}
};

class Gadget
{
private:
	void checkOut() {}
};

class MP3: public Item, public Gadget {};

int main()
{
	MP3 mp;
	// 에러! 어떤 함수인지 모호함(ambiguous)
	mp.checkOut();

	// Item 의 checkOut 
	mp.Item::checkOut();

	// 에러! private 임
	mp.Gadget::checkOut();
}

접근 지정자가 다르더라도, 최적 일치 함수를 찾는 것이 먼저이므로, 모호성 에러가 발생한다.

"죽음의 MI(multiple inheritance) 마름모꼴" 문제도 있다.

class File { ... };
class InputFile: public File { ... };
class OutputFile: public File { ... };

class IOFile: public InputFile, public OutputFile { ... };

이렇게 되면, IOFile 클래스는 File 클래스의 경로 갯수 만큼의 File 객체를 가지게 된다. 가상 상속(virtual inheritance) 를 사용하면, 위와 같은 데이터 멤버의 중복 생성을 막을 수 있다.

class File { ... };
class InputFile: virtual public File { ... };
class OutputFile: virtual public File { ... };

class IOFile: public InputFile, public OutputFile { ... };

하지만 가상 상속은 느리고 비싸다. 그러므로 웬만하면 피하는 것이 좋다.

MI 도 마찬가지로 단점이 많다고 알려진 방식이지만, 다음 예시를 보면서 MI 를 사용했을 때 이득을 볼 수 있는 시나리오를 하나 익혀두도록 하자. 인터페이스 클래스로부터 public 상속을 시킴과 동시에 구현을 돕는 클래스로부터 private 상속을 시키는 케이스를 볼 것이다.

class IPerson
{
public:
	virtual ~IPerson();

	virtual std::string name() const = 0;
	virtual std::string birthDate() const = 0;
};
class DatabaseID { ... };

class PersonInfo
{
public:
	explicit PersonInfo(DatabaseID pid);
	virtual ~PersonInfo();

	virtual const char* theName() const;
	virtual const char* theBirthDate() const;
	
	virtual const char* valueDelimOpen() const;
	virtual const char* valueDelimClose() const;
};

// 다중 상속 사용
class CPerson: public IPerson, private PersonInfo
{
public:
	explicit CPerson(DatabaseID pid) : PersonInfo(pid) {}

	virtual std::string name() const
	{
		return PersonInfo::theName();
	}
	virtual std::string birthDate() const
	{
		return PersonInfo::theBirthDate();
	}

private:
	// 가상 함수들에 대한 재정의 버전을 만듦 (private 상속의 이점)
	// 객체 합성의 경우, 가상 함수의 재정의를 못하는 설계\
	const char* valueDelimOpen() const { return ""; }
	const char* valueDelimClose() const { return ""; }
};

 

Comments