서적 정리/Effective C++

36.상속받은 비가상 함수를 파생 클래스에서 재정의하는 것은 절대 금물!

민돌이2 2021. 12. 22. 00:31

이 챕터 많이 기다렸다.

당연시하게 기본 클래스의 비가상 함수는 파생 클래스에서 재정의하지 않았다. 하지만 이 책의 33장에서 C++ 이름 가리기를 보면서 비가상 함수를 재정의해도 사용은 될 텐데? 싶어서 테스트해보니 아무문제 없이 컴파일 됐고 문제없이 작동한다. 왜 금지하는지 알아보자.

 

기본 클래스로 Base라는 클래스를 만들고 Base 클래스 안에 비가상 함수 mf 함수를 선언하고 Base 클래스를 public 상속하는 Derived 클래스에서 mf 함수를 재정의한다고 가정하자.

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

class Derived : public Base
{
public:
	void mf() { cout << "Derived mf" << endl; }
};

int main()
{
	Derived x;
	Base* pb = &x;
	Derived* pd = &x;

	pb->mf(); //Base::mf를 호출
	pd->mf(); //Derived::mf를 호출
	
	system("Pause");
	return 0;
}

출력 결과

Derived의 객체인 x의 포인터로 Base 객체 pb와 Derived 객체 pd를 선언했다.

Derived의 객체의 주소를 사용하기에 pb->mf() 또한 Derived::mf를 호출 할 것이라 생각할 수 있지만, Base::mf 및 Derived::mf가 호출되는 이유는 비가상 함수는 정적 바인딩(static binding)으로 묶이기 때문이다(37장 참고).

pb는 Base에 대한 포인터 타입으로 선언되었기 때문에 pb를 통해 호출되는 비가상 함수는 항상 Base 클래스에 정의되어 있을 것이라고 결정해 버린다는 것이다.

반면 가상 함수는 동적 바인딩(dynamically binding)으로 묶인다(37장 참고). 만약 mf 함수가 가상 함수였다면 mf 함수가 pb에서 호출되던 pd에서 호출되든 pb 와 pd가 가리키는 객체 타입은 Derived이므로 Derived::mf()가 호출된다. 

결론적으로 비가상 함수를 오버라이드하면 일관성 없는 동작을 보인다.

솔직히 나는 당연시하게 이런 결과가 발생할 것이라 생각해서 이상한점을 느끼지 못했다.

 

비가상 멤버 함수는 클래스 파생에 관계없는 불변동작을 정해 두는 것이다.

이를 정리하자면

1.기본 클래스의 객체에 해당되는 모든 것들이 파생 클래스의 객체에 그대로 적용된다.

2.기본 클래스에서 파생된 클래스는 비가상 함수의 인터페이스와 구현을 모두 물려 받는다.

 

이것만은 잊지 말자

상속받은 비가상 함수를 재정의하는 일은 절대로 하지 말자.

728x90