C++의 다 중 상속 실현 과 자바 의 차이

다 중 문제
필 자 는 학교 모집 면접 에서 유명한 질문 인'C++자바 와 어떻게 다 형 을 실현 하 느 냐'는 질문 을 받 고 불행 하 게 도 전복 되 었 다.너무 유명 해서 오히려 준 비 를 하지 않 고 허 함 수표 와 관련 이 있다 는 것 만 알 고 있다.면접 후 C++와 자바 다 중 실현 의 공통점 과 차이 점 을 비교 해 기록 했다.
C++다 중 가상 포인터 구현
먼저 C++.다 중,즉 하위 클래스 가 부모 클래스 구성원 함수 에 대해 재 작성(Override)한 후,하위 클래스 지침 을 부모 클래스 에 할당 한 다음,이 부모 클래스 지침 에 구성원 함 수 를 호출 하고,하위 클래스 재 작성 버 전의 구성원 함 수 를 호출 합 니 다.간단 한 예:

class Parent1 {
  public:
  virtual void sayHello() { printf("Hello from parent1!
"); } }; class Child : public Parent1 { public: virtual void sayHello() { printf("Hello from child!
"); } }; int main() { Parent1 *p = new Child(); p->sayHello(); // get "Hello from child!" }
먼저,바 텀 실현 에 있어 구성원 함 수 는 첫 번 째 매개 변 수 를 대상 지침 으로 하 는 함수 임 을 알 아야 합 니 다.컴 파일 러 는 자동 으로 대상 지침 을 함수 파라미터 에 추가 하고 this 지침 이 라 고 명명 합 니 다.그 밖 에 일반 함수 와 본질 이 다 르 지 않 습 니 다.비 다 중 구성원 함수 호출 은 비 구성원 함수 호출 과정 과 기본적으로 일치 하 며,매개 변수 목록(매개 변수 목록 에 대상 포인터 형식 포함)과 함수 명 에 따라 컴 파일 할 때 실제 호출 함 수 를 확정 합 니 다.
다 형 을 실현 하기 위해 서 는 대상 포인터 유형 에 따라 함수 서명 을 추정 할 수 없습니다.즉,예 에서p->sayHello() 이 줄 코드 는 실행 할 때 p 의 유형 에 따라 호출 된 함수 가Parent::sayHello 인지 Child:sayHello인지 확인 할 수 없습니다.다 중 메커니즘 에서 모든 종류의 부모 클래스 와 하위 클래스 는 데이터 구조 에서 하나의 지침 을 더 가 져 가 야 한다.이 지침 은 이러한 종류의 가상 함수 표를 가리킨다.
클래스 의 가상 함수 표 는 다시 쓸 수 있 는 모든 함수 포인터 표 입 니 다.대상 이 만 들 때 실제 유형 에 따라 가상 함수 포인터 가 가리 키 는 가상 함수 목록 을 결정 합 니 다.예 를 들 어 위의 예 에서 Parent 1 과 Child 류 의 가상 함수 목록 은 모두 하나의 함수 만 있 는데 각각Parent1::sayHello Child::sayHello이다.컴 파일 러 는 컴 파일 할 때 함 수 를'가상 함수 표 의 N 번 째 함수 참조'라 는 명령 으로 호출 할 것 이다.예 를 들 어 본 사례 에서'가상 함수 표 의 첫 번 째 함수 참조'로 번역 할 것 이다.실행 할 때 가상 함 수 표 의 진정한 함수 지침 을 읽 습 니 다.실행 시 CPU 대 가 는 기본적으로 포인터 참조 와 다음 표 접근 입 니 다.
Parent 1 과 Child 대상 모두 사용자 정의 데이터 구조 가 없습니다.다음 코드 를 실행 하면 Parent 1 과 Child 대상 의 실제 데이터 구조 크기 가 모두 8 바이트 인 것 을 확인 할 수 있 습 니 다.즉,가상 함수 목록 지침 만 있 습 니 다.Parent 1 과 Child 1 대상 을 64 비트 정수 로 출력 하면 p1,p2 의 값 이 같 고 p3 는 앞의 두 가지 와 다르다.이 값 도 상응하는 종류의 허함 수표 주소 이다.

Parent1* p1 = new Parent1();
Parent1* p2 = new Parent1();
Parent1* p3 = new Child();
printf("sizeof Parent1: %d, sizeof Child: %d
", sizeof(Parent1), sizeof(Child)); printf("val on p1: %lld
", *(int64_t*)p1); printf("val on p2: %lld
", *(int64_t*)p2); printf("val on p3: %lld
", *(int64_t*)p3);
C++다 중 상속
매우 재 미 있 는 문제 가 있 습 니 다.C++다 중 상속 이 발생 할 때 다 중 태 를 어떻게 지원 하 는 지.방금 언급 한 다 중 원 리 는 컴 파일 러 가 구성원 함 수 를'가상 함수 표 의 N 번 째 함수 참조'로 컴 파일 하 는 것 이다.가상 함수 표 는 대상 데이터 구조 에서 의 위치 와 가상 함수 목록 에서 의 몇 번 째 함 수 를 컴 파일 할 때 모두 확인 해 야 한다.다 중 계승 대상 이 하나의 가상 함수 목록 만 있다 면 부모 류 의 가상 함수 목록 의 위치 가 충돌 할 것 입 니 다.만약 여러 개의 가상 함수 목록 이 있다 면,컴 파일 할 때 가상 함수 목록 포인터 가 데이터 구조 에 있 는 위 치 를 확정 하기 어렵다.C++는 매우 정교 한 방법 을 취 했다.모든 부모 류 의 데이터 구조(가상 포인터 목록 포함)를 이 대상 의 데이터 구조 에 순서대로 배열 하고 이 대상 의 지침 은 데이터 구조의 시작 위 치 를 정상적으로 가리킨다.포인터 가 유형 변환 이 발생 할 때 C+컴 파 일 러 는 포인터 의 값 을 가능 한 한 조정 하여 이 포인터 유형 이 대응 해 야 할 위 치 를 가리 키 도록 합 니 다.지침 의 값 은 이 과정 에서 변화 가 생 겼 다.
예 를 들 어 Child 류 는 Parent 1,Parent 2 두 종 류 를 계승 하고 Child 포인터 가 Parent 1 포인터 로 전 환 될 때 포인터 의 값 을 조정 하지 않 습 니 다.Parent 1 은 Child 의 첫 번 째 부류 이기 때 문 입 니 다.그러나 Child 를 Parent 2 로 변환 할 때 는 Parent 1 데이터 구조 길이 의 값 을 추가 해 해당 Parent 2 데이터 구조 시작 위 치 를 가리 키 도록 지침 을 지정 해 야 한다.이 예 에서 Parent 1 데이터 구 조 는 가상 함수 목록 포인터 만 있 고 64 비트 기기 의 길 이 는 8 입 니 다.따라서 Child 포인터 가 Parent 2 포인터 로 바 뀌 었 을 때 그 값 이 8 증가 하 였 습 니 다.

class Parent1 {
  public:
  virtual void sayHello() { printf("Hello from parent1!
"); } }; class Parent2 { public: virtual void sayHi() { printf("Hi from Parent2!
"); } }; class Child : public Parent1, public Parent2 { public: virtual void sayHello() { printf("Hello from child!
"); } virtual void sayHi() { printf("Hi from child!
"); } }; int main() { Child *p = new Child(); printf("size of Child: %d", sizeof(Child)); printf("pointer val as Child*: %lld
", int64_t(p)); printf("pointer val as Parent1*: %lld
", int64_t((Parent1*)p)); printf("pointer val as Parent2*: %lld
", int64_t((Parent2*)p)); }
이 코드 를 실행 하면 Child 데이터 구조의 크기 가 16,즉 두 개의 지침 으로 늘 어 난 것 을 발견 할 수 있 습 니 다.또한 포인터 의 값 은 두 번 의 유형 이 바 뀔 때 다 르 고 64 비트 기기 에서 8 개의 바이트,즉 Parent 1 의 데이터 구조 크기 가 다르다.또한 p 를 Void 포인터 로 변환 하고 Parent 포인터 로 변환 하면 컴 파 일 러 가 이 오프셋 을 정확하게 추정 하지 못 하고 정의 되 지 않 은 행동 이 발생 합 니 다.
이 특성 은 사실 매우 재 미 있 는 사실 을 설명 한다.C+컴 파일 러 는 컴 파일 할 때 포인터 의 오프셋 을 추정 할 수 있다.그러면 컴 파일 러 도 이 포인터 가 대상 을 가리 키 는 실제 유형 을 추정 할 수 있 을 것 이다.그렇다면 컴 파일 할 때 대상 의 실제 유형 을 추정 할 수 있 는 이상 가상 함수 표를 사용 하면 무슨 소 용이 있 습 니까?정확 한 함수 호출 을 직접 추정 하면 되 지 않 습 니까?문 제 는 컴 파일 할 때 다 중 함수 호출 을 추정 하면 서로 다른 유형의 대상 에 게 서로 다른 바 이 너 리 코드 를 생 성 하 는 것 을 의미한다.같은 줄 코드 는 포인터 값 에 따라 함수 호출 이 다 릅 니 다.이렇게 되면 제3자 라 이브 러 리 가 소스 코드 를 제공 하여 관련 추정 을 해 야 한 다 는 것 을 의미한다.템 플 릿 라 이브 러 리 와 유사 하 다.이것 은 모두 받 아들 일 수 없 는 것 이기 때문에 가상 함수 목록 은 여전히 필요 하 다.가상 함수 목록 을 통 해 포인터 코드 를 사용 하면 일치 하 는 기계 코드 를 만 들 수 있 습 니 다.
다른 측면 에서 볼 때 컴 파일 러 는 완전한 App 을 컴 파일 할 때 모든 변수의 실제 유형 을 추정 할 수 있 지만 너무 많은 문맥 과 연결 해 야 합 니 다.코드 를 컴 파일 하려 면 이 코드 가 파 라 메 터 를 입력 해 야 합 니 다.형식 을 제외 한 컨 텍스트 정 보 를 입력 하고 컨 텍스트 정보 에 따라 바 이 너 리 파일 을 생 성 해 야 합 니 다.이것 은 받 아들 일 수 없습니다.
자바 다 중 비교
자바 의 다 중 체 제 는 C+보다 간단 하기 때문에 이론 적 으로 C+의 체 제 를 사용 하여 자바 다 형 을 실현 할 수 있다.그러나 C++는 자바 와 결정적 인 차이 가 있 습 니 다.C++는 부모 구성원 에 게 가상 키 워드 를 수정 해 야 다시 쓸 수 있 습 니 다.이 는 컴 파일 러 가 부모 클래스 를 컴 파일 할 때 그 함수 들 이 재 작성 될 수 있 음 을 확인 할 수 있 기 때문에 재 작성 이 불가능 한 함수 에 대해 직접 컴 파일 할 때 호출 할 구체 적 인 함 수 를 결정 하고 재 작성 가능 한 함수 에 대해 서 는 가상 포인터 로 처리 할 수 있다 는 것 을 의미한다.자바 의 방법 은 기본적으로 다시 쓸 수 있 기 때문에 자바 방법 호출 은 모두 가상 함수 목록 을 조회 하 는 과정 을 거 쳐 C++다시 쓰 지 않 는 함수 보다 비용 이 많이 든다 고 볼 수 있 습 니 다.
자바 는 다 중 계승 을 지원 하지 않 지만 자바 는 인터페이스 인 터 페 이 스 를 지원 합 니 다.인 터 페 이 스 는 다 중 계승 과 비슷 한 점 이 있 습 니 다.가상 함수 표를 간단하게 사용 하여 찾 을 수 없습니다.클래스 는 실 현 된 모든 인터페이스 에 가상 함수 목록 을 만들어 야 합 니 다.C++와 유사 합 니 다.OpenJDK 문서 지적클래스 정의 에서 인터페이스 의 가상 함수 목록 을 찾 는 방법 은 매우 거 칠 습 니 다.클래스 가 실 현 된 모든 인터페이스 목록 에서 찾 습 니 다.문서 에서 진정한 다 중 상속 은 보기 드 문 것 으로 통상 적 으로 단일 상속 으로 귀 결 될 수 있다 고 지적 했다.이 과정 에 대해 여러 가지 최적화 가 있 을 수 있 으 므 로 필 자 는 깊이 이해 하지 못 했다.
자바 와 C++의 차이 점 을 생각 합 니 다.C++가 실행 되 지 않 았 을 때 유형 은 컴 파일 러 가 컴 파일 할 때 포인터 가 가리 키 는 위치 에 대상 의 정확 한 데이터 구조 가 있 도록 최선 을 다 합 니 다.하위 포인터 값 을 부모 포인터 변수 에 할당 할 때 컴 파 일 러 는 최선 을 다 해 조정 하지만 Void 포인터 값 등 이 발생 하면 컴 파 일 러 는 포인터 가 가리 키 는 위치 에 정확 한 대상 데이터 구조 가 있 음 을 보장 할 수 없다.이 단 계 는 문법 에 오류 가 없 으 면 바로 오 류 를 보고 하지 않 고 컴 파일 러 도 문제 가 발생 할 수 있 는 지 확인 할 수 없 으 며 이 지침 이 실제 적 으로 인용 을 풀 때 까지 기 다 려 야 오 류 를 보고 할 수 있다.자바 가 실 행 될 때 유형 이 있 습 니 다.대상 을 다른 유형의 변수 에 할당 할 때 실 행 될 때 유형 검 사 를 합 니 다.정확 한 유형 계승 관계 가 없 으 면 할당 타 임 스 가 잘못 될 수 있 습 니 다.
또한 자바 의 인터페이스 와 C++의 다 중 계승 을 비교 해 보면 인터페이스 가 실 행 될 때 C++다 중 계승 보다 시간 비용 이 훨씬 많은 것 을 발견 할 수 있다.그러나 C++다 중 계승 은 모든 부모 클래스 에 지침 을 추가 하고 컴 파일 러 는 컴 파일 할 때 더 많은 작업 을 해 야 합 니 다.자바 는 C++보다 더 강 한 언어 입 니 다.
C++의 다 중 상속 실현 과 자바 의 차이 에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.더 많은 관련 C++다 중 상속 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 바 랍 니 다!

좋은 웹페이지 즐겨찾기