객체지향 구문의 구현
같은 호출문이 실행 중에 알맞은 메서드를 찾아갑니다. 실제로는 어떻게 찾을까?
클래스 인스턴스 레코드
객체는 변수 값을 줄지어 담은 칸
구현에서 풀 것은 둘입니다. 변수를 어떻게 담을지와 메서드를 어떻게 찾을지입니다. 첫째의 답이 클래스 인스턴스 레코드(class instance record, CIR)입니다. 객체마다 따로 가지는 인스턴스 변수의 값을 줄지어 담습니다.
칸의 순서가 컴파일할 때, 곧 실행 전에 번역할 때 정해지므로 5강의 레코드(여러 값을 묶은 것)처럼 번호로 바로 찾습니다. 값은 예시입니다.
상속과 CIR
자식의 변수는 부모 것 뒤에 붙습니다
클래스에 부모가 있으면, 서브클래스의 인스턴스 변수는 부모의 CIR 뒤에 덧붙습니다. Circle 객체는 Shape와 같은 자리에 x, y를 두고 그 뒤에 radius를 둡니다.
4편의 객체 슬라이싱에서 잘려 나간 것이 이 뒷부분입니다.
가상 메서드 테이블
객체에는 포인터 하나만
둘째의 답입니다. 동적으로 바인딩할 메서드의 코드 위치를 표에 모아 둡니다. 이 표가 가상 메서드 테이블(virtual method table, vtable)이고, 객체의 CIR에는 이 표를 가리키는 포인터만 넣습니다.
정적으로 바인딩하는 메서드는 표에 넣지 않습니다. 값은 예시입니다.
상속과 vtable
덮어쓴 칸만 바뀝니다
서브클래스의 vtable은 부모의 것에서 출발합니다. 그대로 물려받은 move 칸은 부모와 같은 코드를 가리키고, 오버라이딩(덮어쓰기)한 draw 칸만 바뀝니다. 새 메서드는 끝에 덧붙습니다.
그래서 draw는 어느 클래스의 표에서나 1번 칸입니다. 번호는 예시입니다.
호출을 따라가면
객체 → 표 → 칸 → 코드
컴파일러는 s.draw()를 '표의 1번 칸을 불러라'로 바꿔 둡니다. 실행 중에는 s가 가리키는 객체의 표로 가므로, 같은 호출문이 다른 메서드에 닿습니다.
표의 시작에서 몇 번째 칸인지를 오프셋(offset)이라고 합니다.
동적 바인딩의 값
표를 거치는 만큼 더 듭니다
정적으로 바인딩한 호출은 코드로 곧장 갑니다. 동적으로 바인딩한 호출은 객체와 표를 거쳐 갑니다. 6편에서 C++가 동적 바인딩을 골라 쓰게 한 까닭이 이 차이입니다.
다음 편