C++의 다 중 및 가상 함수 의 내부 구현 방법

7168 단어 c다 형가상 함수
1.다 형 이란 무엇 인가
다 태 성 은 간단하게'하나의 인터페이스,다양한 행위'로 요약 할 수 있다.
즉,서로 다른 대상 에 게 같은 메 시 지 를 보 내 면 서로 다른 대상 이 받 을 때 서로 다른 행동(즉 방법)을 하 는 것 이다.모든 대상 이 자신의 방식 으로 공 통 된 소식 에 호응 할 수 있다 는 것 이다.메시지 란 함 수 를 호출 하 는 것 이다.서로 다른 행 위 는 서로 다른 실현,즉 서로 다른 함 수 를 실행 하 는 것 을 말한다.이것 은 같은 코드 로 서로 다른 동작 을 실현 하 는 일반적인 기술 이다.이것 은 대상 을 대상 으로 프로 그래 밍 하 는 우월성 을 구현 하 였 다.
다 태 는 두 가지 로 나 뉜 다.
(1)컴 파일 시 다 중:함수 의 리 셋 과 템 플 릿 을 통 해 이 루어 집 니 다.
(2)실행 시 다 중:주로 가상 함 수 를 통 해 이 루어 집 니 다.
2.몇 가지 관련 개념
(1)덮어 쓰기,다시 쓰기(override)
override 는 기본 클래스 의 한 구성원 함 수 를 가상 함수 로 하고 파생 류 는 또 하나의 구성원 함 수 를 정의 하 며 함수 체 를 제외 한 나머지 부분 은 모두 기본 클래스 의 구성원 함수 와 같 습 니 다.함수 이름 만 같 고 형 삼 또는 반환 유형 이 다 르 면 override 가 아니 라 hide 라 고 할 수 없습니다.
(2)과부하(overload)
같은 작용 역 에서 태 어 난 여러 함수 의 이름 은 같 지만 형 삼 이 다른 함 수 를 가리킨다.컴 파일 러 는 컴 파일 할 때 실제 인삼 의 개수 와 유형 을 통 해 최종 호출 함 수 를 선택 합 니 다.
(3)숨 기기(숨 기기)
두 가지 로 나 뉜 다.
1)부분 변수 나 함수 가 전역 변수 나 함 수 를 숨 겼 습 니 다.
2)파생 류 는 기본 클래스 와 같은 이름 의 구성원 함수 나 구성원 변 수 를 가지 고 있다.
결과:전역 또는 기본 클래스 의 변수,함수 가 보이 지 않 습 니 다.
3.몇 가지 간단 한 예

/****************************************************************************************************** 
* File:PolymorphismTest 
* Introduction:         。 
* Author:CoderCong
* Date:20141114 
* LastModifiedDate:20160113 
*******************************************************************************************************/ 
#include "stdafx.h" 
#include <iostream> 
using namespace std; 
class A 
{ 
public: 
  void foo() 
  { 
    printf("1
"); } virtual void fun() { printf("2
"); } }; class B : public A { public: void foo() // foo , , { printf("3
"); } void fun() // { printf("4
"); } }; int main(void) { A a; B b; A *p = &a; p->foo(); // 1。 p->fun(); // 2。 p = &b; p->foo(); // 1。 p ,p->foo 。 p->fun(); // 4。 。 p , 。p->fun 。 , return 0; }
4.운행 시 다 형 및 가상 함수 의 내부 실현
위의 몇 가지 간단 한 예 를 보고 나 는 문득 크게 깨 달 았 다.알 고 보 니 이것 이 다 형 이 었 다.이렇게 간단 하고 알 겠 다!
자,그럼 예 를 하나 더 봅 시다.

class A 
{ 
public: 
  virtual void FunA() 
  { 
    cout << "FunA1" << endl; 
  }; 
  virtual void FunAA() 
  { 
    cout << "FunA2" << endl; 
  } 
}; 
class B 
{ 
public: 
  virtual void FunB() 
  { 
    cout << "FunB" << endl; 
  } 
}; 
class C :public A, public B 
{ 
public: 
  virtual void FunA() 
  { 
    cout << "FunA1C" << endl; 
  }; 
}; 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
  C objC; 
  A *pA = &objC; 
  B *pB = &objC; 
  C *pC = &objC; 
 
  printf("%d %d
", &objC, objC);   printf("%d %d
", pA, *pA);   printf("%d %d
", pB, *pB);   printf("%d %d
", pC, *pC);   return 0; }
실행 결과:
5241376 1563032
5241376 1563032
5241380 1563256
5241376 1563032
세심 한 동 지 는 분명히 pB 에 문제 가 생 긴 것 을 발 견 했 을 것 입 니 다.왜 모두 obsc 를 가리 키 는 지침 이 고 pB 는 다른 사람의 값 과 다 릅 니까?
컴 파일 러 에 문제 가 생 긴 거 아니에요?
당연히 아니 지!우 리 는 먼저 결론 을 말한다.
(1)모든 가상 함 수 를 포함 하 는 클래스 는 가상 테이블(virtual table)을 생 성 합 니 다.이 표 는 대상 의 동적 유형 을 기록 하여 이 대상 의 가상 구성원 함 수 를 실행 할 때 진정 으로 실행 하 는 구성원 함 수 를 결정 합 니 다.
(2)여러 개의 기본 클래스 가 있 는 대상 에 대해 여러 개의 가상 표 가 있 고 모든 기본 클래스 는 하나의 가상 표 에 대응 하 는 동시에 가상 표 의 순서 와 계승 시의 순서 가 같다.
(3)모든 클래스 대상 이 사용 하 는 메모리 에서 가상 포인터 가 맨 앞 에 있 고 모든 가상 포인터 가 대응 하 는 가상 시 계 를 가리킨다.
먼저 간단 한 단일 기류 부터 말하자면:

class A 
{ 
public: 
  virtual void FunA() 
  { 
    cout << "FunA1" << endl; 
  } 
  virtual void FunA2() 
  { 
    cout << "FunA2" << endl; 
  } 
}; 
 
class C :public A 
{ 
  virtual void FunA() 
  { 
    cout << "FunA1C" << endl; 
  }
}; 
int _tmain(int argc, _TCHAR* argv[]) 
{ 
  A *pA = new A; 
  C *pC = new C; 
  typedef void (*Fun)(void); 
 
  Fun fun= (Fun)*((int*)(*(int*)pA)); 
  fun();//pA         
  fun = (Fun)*((int*)(*(int*)pA) +1); 
  fun();//pA         
   
  fun = (Fun)*((int*)(*(int*)pC)); 
  fun();//pC         
  fun = (Fun)*((int*)(*(int*)pC) + 1); 
  fun();//pC         
  return 0; 
}
실행 결과:
FunA1
FunA2
FunA1C
FunA2
좀 어 지 럽 죠?괜 찮 습 니 다.나 는 조금씩 설명 한다.pA 는 A 의 대상 에 대응 하고 우 리 는 이런 시 계 를 그 릴 수 있다.
      
이것 이 바로 대상*pA 의 가상 표 입 니 다.두 개의 가상 함 수 는 성명 순서 로 배열 되 어 있 습 니 다.pA 는 대상*pA 를 가리 키 며,*(int*)pA 는 이 가상 표를 가리 키 며,(Fun)*(int*)(*(int*)pA)는 FunA 를 가리 키 며,마찬가지 로(Fun)*(int*)(*(int*)pA)+1 은 FunA 2 를 가리킨다.그래서 앞의 두 가지 결과 가 나 왔 다.
뒤의 두 가지 결과 에 근거 하여 우 리 는*pC 의 허 표를 아래 그림 과 같이 추측 할 수 있다.
      
C 에 있 는 FunA 가 A 에 있 는 FunA 를 다시 쓰기(override)했 기 때문에 가상 테이블 에 있 는 가상 함수 의 주소 도 다시 썼 다 는 것 이다.
바로 이렇다.이것 이 바로 다 태 적 실현 의 내부 메커니즘 이다.
우 리 는 다시 최초의 문제 로 돌 아 왔 다.왜*pB 에 문제 가 생 겼 습 니까?
위의 결론 에 따 르 면 우 리 는 대담 하 게 추측 했다.C 는 A,B 에서 파생 된 것 이기 때문에 object 는 두 개의 가상 표 가 있 고 표 의 순서 로 인해 pA,pC 는 모두 A 에 대응 하 는 가상 표를 가리 키 며 pB 는 B 에 대응 하 는 가상 표를 가리킨다.실험 을 해서 우리 의 추측 이 정확 한 지 검증 하 자.
우 리 는 A,B,C 류 를 바 꾸 지 않 고 문제 중의 main 을 바 꿉 니 다.

int _tmain(int argc, _TCHAR* argv[]) 
{ 
  C objC; 
  A *pA = &objA; 
  B *pB = &objC; 
  C *pC = &objC; 
   
  typedef void (*Fun)(void); 
 
  Fun fun = (Fun)*((int*)(*(int*)pC)); 
  fun();//          
  fun = (Fun)*((int*)(*(int*)pC)+1); 
  fun();//          
  fun = (Fun)*((int*)(*((int*)pC+1))); 
  fun();<span style="white-space:pre"> </span>//          
  fun = (Fun)*((int*)(*(int*)pB)); 
  fun();//pB           
  return 0; 
}
하하,우리 의 추측 과 완전히 일치한다.
FunA1C
FunA2
FunB
FunB
우 리 는 이러한 허 함수 도 를 그 릴 수 있다.
        
일단 이렇게 이해 하면 컴 파일 러 가 B*pB=&object 를 실행 할 때 할당 만 하 는 것 이 아니 라 해당 하 는 최적화 를 하여 pB 를 두 번 째 가상 표 로 가 리 켰 다.
이렇게 많은 말 을 했 습 니 다.저 는 허 함수 의 실현 원 리 를 간단하게 설 명 했 을 뿐 입 니 다.그런데 도대체 대상 내부 의 메모리 구 조 는 어떻게 됩 니까?클래스 데이터 구성원 과 여러 개의 가상 테이블 의 구체 적 인 메모리 레이아웃 은 어 떻 습 니까?컴 파 일 러 는 어떻게 값 을 부여 할 때 최 적 화 를 했 습 니까?나중에 얘 기 할 게 요.
이상 은 여러분 에 게 가 져 다 준 C++의 다 태 와 허 함수 의 내부 실현 방법의 모든 내용 입 니 다.많은 응원 부 탁 드 리 겠 습 니 다~

좋은 웹페이지 즐겨찾기