2025/03/10 ch.10 상속 활용하기
상속을 활용하면 기존 클래스를 바탕으로 새 클래스를 정의할 수 있다.
현실에 존재하는 대상은 대부분 계층 구조에 속한다
상속을 활용하면 기존 클래스를 바탕으로 새 클래스를 정의할 수 있다.
현실에 존재하는 대상은 대부분 계층 구조에 속한다 is - a
이러한 관계를 만들때의 한가지 방법은 기존 클래스를 복사하여 다른 클래스에 붙여넣는것
- 추후 원본 클래스의 버그를 수정해도 새 클래스에 반영되지 않음
- 다형성 관계를 이룰 수 없다
- 진정한 is - a 관계가 아니다
- 간혹 원본 클래스에 직접 접근할 수 없어 복사 자체가 안될 수도 있음
클래스 확장하기
C++에서 클래스를 정의할 때 컴파일러에 기존 클래스를 상속, 파생, 확장 한다고 선언할 수 있음.
새로 만들 클래스에 기존 클래스의 데이터 멤버와 메서드를 자동으로 가져올 수 있다.
이때 원본 클래스를 부모 클래스(베이스 클래스, 슈퍼 클래스) 라고 부름
기존 클래스를 확장한 다른 클래스는 자식 클래스(파생 클래스, 서브 클래스) 라고 부름
다른 코드에서 볼때 자식 타입 객체는 부모 타입 객체이기도 하다. (자식이 부모를 상속한 것이기 때문)
자식 클래스는 부모클래스의 메서드를 사용할 수 있지만 부모클래스는 자식 클래스의 메서드를 사용할 수 없으니 주의
부모클래스의 포인터는 자식 클래스도 가리킬 수 있다.
하지만 자식 클래스의 포인터로는 부모 클래스를 가리킬 수 없다
기본적으로 클래스의 데이터 멤버는 private로 만들고 게터 세터로 접근하게 만드는것이 바람직함
public으로 선언하면 누구나 접근할 수 있고, protected로 선언하면 파생 클래스에서만 접근할 수 있음
-> 캡슐화 수준을 최대한 높인다
상속방지
class Base final
{
};
C++에서 클래스를 정의할 때 final 키워드를 붙이면 다른 클래스가 이 클래스를 상속할 수 없다.
vitual 키워드
C++에서는 베이스 클래스에서 버츄얼 키워드로 선언된 메서드만 파생 클래스에서 오버라이드 할 수 있다.
메서드 오버라이딩 문법
파생 클래스에서 베이스 클래스의 메서드를 오버라이드 하려면 베이스 클래스에 나온 것과 똑같이 선언하고 맨 뒤에 virtual 대신 override를 붙임
메서드나 소멸자를 한번 virtual로 지정하면 그 후 정의하는 모든 파생 클래스에서도 virtual 상태를 유지함. 파생클래스에서 virtual 키워드를 제거해도 마찬가지
오버라이드한 메서드는 일반적인 방식으로 호출하면 됨. 부모클래스의 객체로 호출할 수도 있고 자식 클래스의 객체로 호출할수도 있지만 실제 동작은 객체가 속한 클래스에 따라 달라짐
객체 자신은 멤버가 어느 클래스에 속하는지 알기 때문에 virtual로 선언되었다면 가장 적합한 메서드를 호출한다.
# 베이스 클래스에 virtual이란 키워드를 적지 않았다면 오버라이드한 버전이 호출되지 않음
또한 베이스 클래스 타입으로 선언된 레퍼런스나 포인터가 실제로 파생 클래스 타입 객체를 가리킨다 해도, 베이스 클래스에 정의되지 않은 파생 클래스의 데이터멤버나 메서드는 접근 불가!
파생 클래스를 인식해서 적합한 메서드를 호출하는 기능은 포인터나 레퍼런스 객체에만 적용됨
헷갈리는 것
파생클래스를 베이스 클래스 레퍼런스&포인터로 참조하면 기존 데이터 멤버, 메서드 그대로 유지
파생클래스를 베이스 클래스로 캐스트 해버리면 파생에서 새로 정의한것들이 사라짐
-> 슬라이싱
헷갈리지 않게 주의
베이스 클래스의 포인터로 파생 클래스를 참조하면 베이스 클래스에는 파생 클래스의 정보를 알 수 없는건 맞다
하지만 virtual 키워드를 붙임으로써 이 메서드를 오버라이딩하는 파생 클래스가 있다는것을 가정하고 런타임에 파생클래스를 찾아 내려간다. 이것이 동적바인딩
override 키워드
안붙여도 되지만 헷갈리기 쉬우므로 붙여줘라
만약 virtual 키워드를 적지 않았다면?
파생 클래스의 메서드를 실행하면 물론 파생 클래스 버전의 메서드가 실행된다.
하지만 그것은 오버라이드 된것이 아니라 파생 클래스에서 새로 정의된 함수로 취급된다
베이스 클래스의 메서드를 실행하면 오버라이드된 함수를 찾아 내려가는것이 아니라 베이스 클래스의 메서드가 바로 실행되어 버린다
virtual 메서드의 내부 작동 방식
일반적으로 비 버츄얼 메서드의 경우 컴파일 타임에 클래스에 있는 메서드를 모두 담은 바이너리 객체가 생성되고
메서드를 호출하는 부분을 컴파일 시간에 결전된 타입의 코드로 교체함 <- 정적 바인딩
virtual로 선언하면 vtable(가상 테이블)이라 부르는 특수 메모리 영역을 활용해 런타임에 메서드가 호출될 때 가상 테이블을 보고 그 시점에 가장 적합한 버전의 메서드를 실행함 <- 동적 바인딩
virtual 키워드가 필요한 이유
자바같은경우 메서드를 기본적으로 virtual로 만들기 때문에 올바르게 오버라이드 하지만 C++는 아님
vtable은 아무래도 성능을 더 먹으므로 C++는 가상함수를 쓰지 않아도 될 경우 성능을 덜 쓰도록 프로그래머에게 맞긴 것이라고 생각할 수 있음
virtual 소멸자의 필요성
모든 메서드에 virtual을 붙이는것에 회의적인 사람들도 소멸자에만큼은 virtual을 붙이라고 생각한다고 함
파생 클래스의 생성자에서 동적으로 할당한 메모리를 사용하다가 소멸자에서 삭제하도록 작성했을 때 소멸자가 호출되지 않으면 메모리가 해제되지 않는다고 함
파생 클래스에서 동적할당이 발생한 경우
Base 타입으로 선언되었으므로 Base의 생성자 호출-> Derived 생성자 호출 순서로 진행되지만
delete할때는 Base 타입이므로 Base 클래스의 소멸자만 호출된다. 즉 Derived 클래스의 소멸자가 호출되지 않아 동적할당된 메모리가 해제되지 않는다!
특별한 이유가 없거나 클래스를 final로 선언하지 않았다면 소멸자를 포함한 모든 메서드를 virtual로 선언하는게 바람직함. 단 생성자 제외
오버라이드 방지하기
C++는 클래스 전체를 final로 지정하는 기능뿐만 아니라 개별 메서드 단위로 final 지정도 가능
10.2 코드 재사용을 위한 상속
기존 클래스의 내부를 볼 수 없는 상태에서 어떻게 새로운 기능을 지원하게 할 수 있을까?
원하는 기능을 지원하는 파생 클래스와 지원하지 않는 베이스 클래스 사이를 중계하는 인터페이스를 추가한다.
기존 기능을 변경하기?
- 파생 클래스에서 오버라이드 하여 원하는 형태로 동작을 바꾼다
주의할 점
파생 클래스를 작성할 때는 반드시 부모 클래스와 자식 클래스의 상호 작용에 주의해야 함
생성 순서, 생성자 체이닝, 캐스트 등과 관련하여 버그가 많이 발생하는 경향이 있다.
10.3.1 부모 클래스의 생성자
객체는 한 번에 생성되지 않는다. 부모와 함께 생성되고, 부모 객체와 그 안에 담긴 객체부터 생성되어야 한다.
이 규칙은 재귀적으로 적용된다.
부모 클래스에서 디폴트 생성자가 없거나 다른 생성자를 사용하고 싶을때는
자식 클래스에서 생성자 초기자와 같은 방법으로 생성자를 체인으로 엮어줘야 함
~530p