본문 바로가기

Programming/C / C++

[C++] 상속 네번째, 상속된 객체와 포인터와의 관계 (객체 포인터)

 객체 포인터  
 : 객체 포인터란 객체를 가리킬 수 있는 포인터를 의미한다. (객체의 주소 값을 저장할 수 있는 포인터)
  - 예를 들어 A 클래스의 포인터는 A 객체뿐만 아니라, A클래스를 상속하는 파생 클래스 객체의 주소 값도 저장이 가능하다

 다음 상속 관계를 한번 살펴 보도록 하자.
 is-a관계에 의해서 ScholarStd 객체는 Student 객체이자 Person 객체도 동시에 된다.  ("ScholarStd 는 Person 객체이다." 이런 말도 틀린말은 아니다) 그러면 "Student 객체는 ScholarStd  객체이다" 는 성립이 안된다. is-a 관계는 아래쪽으로 성립이 안된다. 우리가 프로그램상에서 서로의 객체를 생성했다고 해보자. 아래의 예제는 is-a 관계에서 위의 그림은 소스코드로 나타낸 것이다. 
여기서 p1,2,3 포인터는 Person 클래스의 포인터니까 Person클래스의 객체를 가리킬 수 있는 포인터가 되는것이다. (*p1는 Person 클래스의 객체를 가리킬 수 있는 포인터) 
- p2라는 포인터는 Student 클래스의 객체를 가리키는 것도 문제가 없을 것이다. 
- p3라는 포인터는 ScholarStd 클래스의 객체를 가리키는 것도 문제가 없을것이다. 

그럼 "Student *s;" 라고 선언할시를 생각해 보자. 이 선언은 ScholarStd 클래스의 객체를 가리키는 것은 문제 없다. (ScholarStd 객체는 Student 객체이자 Person 객체이므로) 하지만 포인터 s는 Person 클래스의 객체를 가리키는 것은 문제가 된다. 왜냐하면 모든 Person 객체들이 다 Student가 아니기 때문이다. 그리고 클래스의 객체를 가리킨다는 의미는 역으로 is-a관계가 성립해야 된다는 걸 의미한다.

 객체 포인터 권한  
  지금까지는 객체가 가리키는 것이 어떤 경우가 합당한지를 알아 봤는데, 이 객체를 가지고 그 안의 함수를 사용하는 경우는 어떨까? 객체 포인터 권한에 대해 알아보자. 
  예를 들어 여기 A라는 클래스가 있다 A는 B에 의해 상속되어 지고, C는 B클래스에 의해 상속되어 진다고 가정하고 각각의 클래스에는 클래스 이름에 맞는 a,b,c() 함수가 존재한다고 해보자. 
 그 후, C 클래스의 객체를 생성한다고 하자. 그러면 C클래스의 객체 안에는 아마도, A클래스내에 선언되어 있는 a()함수도 있을것도 b()도 있고 자신의 클래스의 함수에 c()도 있을것이다. 아래의 그림 처럼 말이다. (아래의 그림처럼 함수 경계선이 있는것은 아니다.) C 클래스의 객체는 B객체이자 A 객체가 되므로 A,B,C 클래스의 포인터를 가지고 이 것을 가리킬수 있을 것이다. 
  이렇게 포인트 변수들이 가리키는 대상이 같을 경우, 만약 포인터 A (a*)가 가리키는 C 객체의 주소값이 0x10번지 라고 하면, 포인터 b,c의 주소값은 0x10번지로 으로 다 똑같다.  
  A클래스의 포인터를 가지고 가리키는 대상이 비록 C객체 이지만, A클래스의 포인터를 가지고 호출 할 수 있는 함수는 A클래스 내에 선언되어 있는 멤버 변수나 멤버함수로 제한적이다.  "A클래스 포인터(A* a) 가 가리키는 대상이 C 객체 일지라도, A 클래스의 포인터로 접근 할 수 있는 영역은 A 클래스내로 제한된다." 라는 이야기 이다.  


 위의 이야기를 염두에 두고 소스코드를 보자.  C클래스의 포인터 c는 C클래스 객체를 가리키고 있다. 그러므로 C클래스의 포인터로 접근할 수 있는 영역은 C클래스내로 제환된다는 이야기 이다. is-a관계에 의해 C는 모든 함수에 접근 권한을 가지게 되므로 아래의 코드는 문제가 없다. 

다음의 코드를 한번 살펴보자. 


 우선 24번째 줄에 있는 B* b = c;  문법을 보자. 여기서 우리는 포인터의 타입이 일치 하지 않는데 어떻게 이런 문법이 가능한가 라는 의구심을 가지게 될것이다. 하지만 앞서 배웠듯이, C클래스의 포인터는 C클래스의 포인터 이자, A,B 클래스의 포인터도 되므로 이런 문법은 성립이 되는 것이다. 
 문제 이야기로 넘어와 보자. 위 예제는 객체 포인터 권한 도입부분에 설명한 부분을 코드로 풀어 놓은 것이다. c,b,a를 출력해보면 같은 주소값을 가짐을 확인할 수 있다. 
  그리고 또 확인해야 할 것이 있다. B클래스의 포인터를 이용해 각각 함수를 호출해 보자. 메인함수에 아래와 같은 코드를 넣어 컴파일해 보자.

위와 같이 컴파일 에러를 확인해 볼수 있다. 왜그런 것일까? 우리는 b라는 포인터가 가리키는 객체가 C클래스의 객체라는 것을 알고 있지만, 컴파일러는 b라는 포인터가 가리키는 대상이 C클래스의 객체라는것을 모른다. (그게 C건 어떤 것이건 아예 모른다.) b라는 포인터가 어느 클래스의 객체를 가리키는지는 runtime에 결정 되기 때문에 컴파일러는 이 대상이 어느 객체를 가리키는지 결정을 못내리는 것이다.

 이와 마찬가지로 A의 클래스 포인터 a 가 가리키는 것이 우리는 C클래스의 객체라는것을 알고 있지만, 컴파일러는 무엇을 가리키는지 알지 못한다. 그래서 컴파일러는 포인터 a가 가리키는 대상이 무엇이든지 a함수의 호출은 전혀 문제가 안될것이라는 판단하에 a함수 호출만을 보장하는 것이다.