클래스에 상속이 없다면 구조체에 동작만 추가한 것에 불과하다.
그것만으로도 절차형 언어에 비하면 놀라운 발전이지만,
상속은 새로운 차원의 기능이 추가된 것이다.
상속을 활용하면 기존 클래스를 바탕으로
새 클래스를 정의할 수 있다.
따라서 클래스는 재사용하거나 확장 가능한 컴포넌트인 것이다.
상속의 강력함을 최대한 잘 활용하기 위해서는
상속에 관련된 구체적인 문법 뿐만 아니라
상속을 최대한 활용하기 위해서는
상속과 관련된 고급 테크닉까지 알아야한다.
상속을 이용한 클래스 구현
현실에서 존재하는 대상의 대부분은 계층 구조를 가진다.
프로그래밍에서도 이와 마찬가지로
클래스를 수정하거나 다른 클래스를 바탕으로
새 클래스를 정의할 때 이러한 관계를 분명히 볼 수 있다.
코드에서 이러한 관계를 다루는 한 가지 방법은
기존 클래스를 복사하여 다른 클래스에 붙여넣는 것이다.
그러면 원본과 약간 다르게
원하는 형태로 수정하는 방식으로 새로운 클래스를 정의할 수 있다.
하지만 이러한 방식은 OOP 프로그래머 입장에서 볼 때
지치거나 짜증날 수도 있는데
이유는 다음과 같다.
1. 나중에 원본 클래스의 버그를 수정해도 새 클래스에 반영되지 않는다.
두 코드는 완전 별개이기 때문이다.
2. 컴파일러는 두 클래스의 관계를 모른다.
따라서 동일한 대상을 바탕으로
서로 다르게 변형한 다형성의 관계를 이룰 수 없다.
3. 진정한 is-a의 관계가 아니다.
새 클래스의 코드의 상당부분이 원본 클래스와 같아서
서로 비슷하지만, 타입이 같은 것은 아니다.
4. 간혹 원본 클래스의 소스코드에 접근할 수 없거나,
컴파일된 바이너리 버전만 있어서
소스 코드를 직접 복사할 수도 없다.
당연히 C++는 진정한 is-a관계를 정의하는 기능을 기본으로 제공한다.
이제 C++에서 제공하는 is-a의 관계의 특성을 살펴본다.
클래스 확장하기
C++에서 클래스를 정의할 때 컴파일러에 기본 클래스를
상속, 파생, 확장한다고 선언할 수 있다.
이렇게 하면 새로 만들어진 클래스에
기존 클래스의 데이터 멤버 및 메소드를 가져올 수 있다.
이때 원본 클래스를 부모 클래스(베이스 클래스 또는 슈퍼 클래스)라 부른다.
그러면 기존 클래스를 확장한
자식 클래스(파생 클래스 또는 서브 클래스)는
부모 클래스와 다른 부분만 구현하면 된다.
C++에서 어떤 클래스를 확장하려면
그 클래스를 정의할 때 다른 클래스를 확장한 것 임을 표시해야 한다.
클라이언트 입장에서의 상속
상속은 반드시 한 방향으로 흐른다는 것을 명심해야한다.
파생 클래스에서는 베이스 클래스를 명확하게 알 수 있지만,
베이스 클래스에서는 파생 클래스의 존재를 알 수 없다.
파생 클래스는 베이스 클래스의 타입을 가지지만
베이스 클래스는 아니기 때문이다.
다음 코드는 컴파일 에러를 발생시킨다.
코드블럭으로 변환
Base myBase;
myBase.someOtherMethod(); // 컴파일 에러! myBase에 someOtherMethod()가 없다!
다른 코드에서 볼 때
객체는 그것이 정의된 클래스 뿐만 아니라
그 클래스의 모든 베이스에도 속한다.
객체에 대한 포인터나 레퍼런스 타입은,
그 객체의 선언문에 나온 클래스 뿐만 아니라
그 클래스의 파생 클래스 타입으로도 지정할 수 있다.
즉 Base 타입 포인터로
파생 클래스인 Derived 객체도 가리킬 수 있다는 말이다.
클라이언트는 기본적으로 Base에 있는
데이터 멤버나 메서드에 접근할 수 있지만
상속을 통해 Base에 적용되는 코드를 Derived에도 적용할 수 있다.
예를 들어 다음 코드는 얼핏 보면
타입이 맞지 않는 것 처럼 보이지만,
정상적으로 컴파일 된다.
코드블럭 변환하기
Base* base { new Derived{} }; // Derived객체를 생성해서 Base 포인터에 저장한다.
하지만 Base 포인터로 Derived의 메서드를 호출할 수는 없다.
예를 들면 다음과 같은 코드이다.
base->someOtherMethod();
객체 타입이 Derived이고,
실제로 someOtherMethod() 도 정의되어 있지만,
선언을 Base타입으로 했기 때문에 컴파일 에러가 발생한다.
컴파일러는 이 객체의 타입이 someOtherMethod()가 없는
Base로 생각할 수 있기 때문이다.
파생 클래스 입장에서 본 상속
파생 클래스를 작성하는 방법이나 동작 관점에서 보면
일반 클래스와 다를 바가 없다.
일반 클래스와 마찬가지로
메서드와 데이터 멤버를 가진다.
다른 것은 private빼고 protected, public과 같은 접근 제한자로
부모 클래스의 메서드와 데이터 멤버를 상속받는 다는 것이다.
접근 제한자를 두는 이유는
기본적으로 캡슐화 수준을 최대화하기 위해서다.
'프로그래밍 언어 > C & C++ 정리' 카테고리의 다른 글
순수 가상 메서드와 추상 베이스 클래스 (0) | 2025.04.18 |
---|---|
virtual 키워드 메서드 (0) | 2025.04.16 |
람다 함수들 (1) | 2025.04.12 |
안정된 인터페이스 만들기 (0) | 2025.04.11 |
C++ 20 중첩 클래스 (0) | 2025.04.07 |