[Effective C++] #36 비 가상 함수의 상속, 바인딩
Scott Meyers의 "Effective C++"를 통해, C++ 구현에 필요한 개념들을 이해하고, 기록하기 위함입니다. 해당 항목은 6장 "상속, 그리고 객체 지향 설계", 항목 36 "상속받은 비 가상 함수를 파생 클래스에서 재정의하는 것은 절대 금물!"에 해당하는 내용입니다.
비 가상 멤버 함수
class A
{
public:
void mf(); // 비 가상 멤버 함수
virtual void mf2(); // 가상 멤버 함수
...
};
class B : public A {...};
기본 클래스에 대한 public 상속은 "is... a" 상속입니다. 이전 항목에서 다뤘던 내용입니다. public 상속이란, 클래스 "B"를 클래스 "A"로부터 pulbic 상속을 통해 파생시켰다면, "B" 타입으로 만들어진 모든 객체는 또한 "A" 타입의 객체가 됩니다. 더불어, A 객체가 가진 모든 것들이 파생 클래스 객체에 적용된다고 단정 짓습니다. 자세한 내용은 아래 링크를 참조해 주세요. 비 가상 멤버 함수 또한 이전 항목에서 다뤘습니다. 이 또한 아래 링크를 참조해 주세요. 파생 클래스는 기본 클래스의 비 가상 멤버 함수에 대한 인터페이스 그리고 필수적인 구현을 상속받습니다. 그리고, 해당 함수에 대한 오버라이드를 제한하죠. 이번 항목에서는 파생 클래스의 비 가상 멤버 함수에 대한 재정의를 주의해야 하는 이유에 대해서 알아보겠습니다.
바인딩
int a = 2;
바인딩이란, 프로그램 소스에 쓰인 각종 내부 요소, 이름, 식별자들에 대해 값 혹은 속성을 확정하는 과정을 일컫는다. 이 과정이 빌드 중에 이루어지면 정적 바인딩이라고 하며, 프로그램 실행 중에 이루어지면 동적 바인딩 이라고 합니다. 예를 들면, 데이터 타입과 변수 명의 결정은 정적 바인딩이며, 변수에 "2"를 대입하는 동작은 동적 바인딩입니다.
정적 바인딩
void B::mf()
{
... // 파생 클래스의 재정의
}
A a;
A *pa = &a;
B *pb = &a;
pa->mf();
pb->mf();
위 예제 코드를 먼저 살펴보겠습니다. 기본 클래스 "A"로 부터 파생된 클래스 "B"는 포인터 "pb"를 통해 "mf()" 함수를 호출하고 있습니다. 하지만, "pa->mf()"의 결과와 "pb->mf()"의 결과는 다릅니다. "B::mf" 함수, 혹은 "pb->mf()"는 클래스 "B"에 정적 바인딩으로 묶이기 때문입니다. 무슨 말인고 하니, "pb"는 클래스 "B"에 대한 포인터 타입으로 선언되어 "pb"를 통해 호출되는 비 가상 함수는 항상 클래스 "B"에 정의되어 있을 것이라고 단정 짓기 때문입니다. 따라서, "mf()" 함수에 대한 일관성을 유지할 수 없게 됩니다. 반면, 기본 클래스의 "mf()"가 가상 멤버 함수라면, 동적 바인딩으로 묶여 "pa"가 호출하든 "pb"가 호출하든 파생 클래스 "B"에서 정의한 "mf()"를 호출하겠죠.
비 가상 함수 동작의 일관성 문제와 더불어, public 상속의 개념을 위반하는 행동으로 해석 할 수 있습니다. 파생 클래스 "B"는 기본 클래스 "A"로부터 public 상속하고 있지만, 비 가상 멤버 함수 "B::mf()"와 "A::mf()"의 구현 내용이 달라지기 때문에 모순이 생깁니다.
상속받은 비 가상 함수를 절대로 재정의 해선 안됩니다.
'언어 > Effective C++' 카테고리의 다른 글
[Effective C++] #38 객체 합성, private 영역, "has-a" ,"is-implemented-in-terms-of" (0) | 2022.02.09 |
---|---|
[Effective C++] #37 가상 함수, 기본 매개변수 (0) | 2022.02.09 |
[Effective C++] #35 Public 가상 함수의 대안, NVI, 전략 패턴 (0) | 2022.02.03 |
[Effective C++] #34 사용자 정의 기본 멤버 함수 (0) | 2022.01.31 |
[Effective C++] #23 비 멤버, 비 프렌드 함수 (0) | 2022.01.28 |