개요
객체지향 프로그래밍의 중요한 목적 중 하나인 코드의 재활용에서 가장 큰 상속.
c++에서 클래스의 상속에 대해서 알아보겠다.
상속
기초 클래스라 불리는 클래스에서 모든 메서드와 멤버들을 상속받아, 파생 클래스를 만들 수 있는 기능을 말한다.
클래스를 선언할 때 클래스 이름 옆에 초기화 리스트처럼 : public ClassName
처럼 쓰면된다.
파생 클래스는 기초 클래스의 private 멤버에 직접 접근할 수 없고 protected 멤버 혹은 public 멤버만 접근 가능하다.
파생 클래스의 객체를 생성할 때, 프로그램은 기초 클래스 생성자를 호출하고 그 다음에 파생 클래스 생성자를 호출한다.
상속받는 데이터들의 초기화는 기초 클래스가 한다. 파생클래스의 객체가 소멸될 때, 파생 클래스의 소멸자부터 호출한다.
기초 클래스 포인터 혹은 참조는 명시적으로 데이터형변환을 안해줘도 파생 클래스의 객체를 지시할 수 있다.
그러나 반대로 작은거에 큰거 담으려 할 때는 안된다.
업캐스팅 다운캐스팅
파생 클래스 참조나 포인터를 기초 클래스 참조나 포인터로 변환하는 것을 업캐스팅이라고 한다.
public 상속에서는 명시적인 데이터형변환이 없어도 언제든 가능하다.
그러나 반대로 다운캐스팅 시에는 명시적인 데이터형변환이 있어야 가능하다.
virtual
만약 객체에 따라 메서드의 행동이 달라진다면 이것을 다형성이라고 부르며 virtual
키워드를 사용하여 해결할 수 있다.
만약 virtual 키워드를 사용하지 않은 경우, 프로그램은 참조형이나 포인터형에 기초하여 메서드를 선택하지만, 사용한다면 객체에 따라서 메서드를 선택한다.
기초 클래스에서 가상으로 선언되었다면 파생클래스에도 자동으로 가상메서드가 된다.
생성자는 가상으로 선언할 수 없다.
또 소멸자에 virtual을 붙여 가상 소멸자로도 많이 사용한다. 이는 파생클래스 객체가 소멸될 때 소멸자들이 올바른 순서로 호출되게해준다.
가상 소멸자
소멸자들이 가상이 아니라면, 포인터형에 해당하는 소멸자만 호출될 것이다. 가상이라면 객체에 해당하는 소멸자가 호출 된다.
즉 기본 클래스의 소멸자부터 호출될 것이고 파생 클래스의 객체는 메모리 누수가 될 것이다.
정적 바인딩과 동적 바인딩
소스 코드에 있는 함수 호출을 특정 블록에 있는 함수 코드를 실행하라는 뜻으로 해석하는 것을 함수이름을 바인딩한다라고 한다.
이는 컴파일러가 수행하는데 컴파일 동안 일어나는 바인딩을 정적 바인딩이라고 부른다.
가상함수는 컴파일 시간동안 어떤 함수를 사용할 지 알 수가 없다. 프로그램 실행할 때 사용자가 객체를 결정할 때 알 수 있다.
따라서 프로그램을 실행할 때 가상 메서드가 어떤 메서드를 실행할 지 정한다. 이를 동적 바인딩이라고 부른다.
왜 두가지가 있는가?
동적 바인딩은 클래스 메서드들을 다시 정의하는 것을 허용하지만 정적결합은 허용하지 않는다.
대부분의 경우에서 동적 바인딩이 좋다고 하는데 왜 디폴트는 정적일까??
효율성과 개념모델 때문이다.
프로그램이 실행 시간에 결정하도록하면 부담이 생긴다. 그리고 정적 바인딩이 프로그램적으로 효율적이기 때문에 디폴트이다.
가상함수는 어떻게 작동하는가
이는 컴파일러가 수행한다. 컴파일러는 각각의 객체에 멤버를 하나씩 추가한다.
멤버는 함수의 주소들로 이루어진 배열을 가리키는 포인터이다. 이를 가상함수 테이블이라고 부른다.
클래스의 객체들으르 위해 선언된 가상 함수들의 주소가 저장되어있다.
파생 클래스가 가상 함수를 다시 정의하지 않으면 테이블은 함수의 오리지널 버전의 주소를 저장한다.
파생 클래스가 다시 정의한다면 테이블에 추가되는 형식이다.
따라서 가상함수를 사용하면 메모리와 실행 속도 면에서 부담이 생긴다.
이 테이블은 객체단위가 아닌 클래스 단위로 존재한다.