C++에서 객체의 의미와 클래스타입의 포인터가 갖는 의미 그리고 다형성에 대해서 살펴보도록하자.
다음은 상속의 개념을 간략화한 그림이다.
(멤버변수,함수) (클래스)
ㅁ ----------- <- 탈것(A)
ㅁㅁ --------- <- 비행기(B)
ㅁㅁㅁ -------- <- 전투기(C)
<그림>-객체-1
색깔별로 특성들이 상속되어진다는 의미인데,
그럼 우리가 살펴보려는 객체와 포인터에대해서 알아봐야겠다.
멤버변수와 함수는 재정의(overriding) 될 수도있고 그냥 재정의 안되고 상속만 되어질 수도있다.
재정의되든 상속만 되어지든, 함수의 경우 같은기능 또는 비슷한 기능을 하는 함수의 이름이 바뀔리없다.
그럼 B 클래스 객체 b를 생성했다면 b가 가르키는 ㅁ는 어느클래스의 ㅁ인가?
물론 B클래스의 ㅁ이다.
그럼 A *a=new C(); a->ㅁ;
이런 선언은 가능한가? 결론적으로 가능하다. A클래스객체포인터가 C객체를 가르킨다.
컴파일러상에서 에러는 없다.(하지만 실행시에 에러가 발생할 일이 있다.)
하지만 위 구문을 처음 접했다면 무슨의미인지 도무지 파악이 안된다. 그런가?
c++에서 약속하고 정의한 법칙을 익히지 않는한 당신이 천재가 아닌한 말이다.
왜 저런 선언을 힘들게 해서 사람 헷갈리게 하는가.?
나도 처음 공부할 때 그랬었다.
하지만 차근차근 이해해 가다보면 아~ 이렇게 대단할 수가 하고 감탄할 때가 오게된다.
바로 자연어와 같은 언어의 법칙이 아닌 논리적으로 짜여진 OOP개념에 감탄할 날이 말이다.
OOP라는 개념은 인간이 오랜기간 시행착오를 거처 만든 문법적 개념이다. 물론 객체지향 언어마다
차이점은 있지만 기본 맥락은 비슷하다.
기본 법칙을이야기하면
1.상위 클래스타입의 포인터객체는 하위 타입의 클래스객체를 가르킬 수가있다 위 선언처럼 말이다.
2.하위클래스 객체는 상위 타입의 멤버함수를 호출할수있다. 물론 그 멤버함수는 public이나 protected으로 선언 되어야하겠지만..
1번을 다시 풀어서 말하자면 하위타입의 객체를 상위클래스타입의 객체에 대입할수있다.
이건 is a 관계인다. 말하자면 B는 A이다가 성립한다는 말이다. 반대로 말하자면 is a 관계이기 때문에 가능하다.
저런 자식에게서 부모클래스로 대입이 묵시적으로 용인되고 컴파일 시에도 에러가 나지 않는 것이다.
OOP를 위한 정의일듯 하다.
**주의 C언어에서는 구조체간에 타입이 맞지 않는다면 대입할 수가 없다.
그건 우리가 C++을 배워나가다보면 OOP와 맞물려 생각하면 어쩔수 없는 생각이라 들 때가 올것이다.
대입은 했지만 우리는 잃어버리는 데이터들이 있을 것이다. a객체포인터가 c객체를를 참조한다고해서 c객
체의 함수호출시 호출이 된다고 생각하면 실수다. 잘려나간 함수호출시 런타임시 에러가 나타날것이다.
다시 문제의 구문을 보자 A *a=new C(); a->ㅁ;
a->ㅁ;
이런 선언시 호출되는 ㅁ도A 클래스의 것이다.
어떻게 된일인가. A객체포인터로 C를 가르키지말고 차라리 A를 가르키는거나 무슨차이가 있는 것인가?
그렇다 차이가없다.
ㅁ <- 탈것(A) a객체
ㅁㅁ <- 비행기(B)
ㅁㅁㅁ <- 전투기(C)
여기서 다형성에대한 추상적인 개념이 나온다. 이런 쓸데없는? 일을 벌이는 이유는 우리가 다형성을 사용하기 위한 수단이 된다.
다형성을 설명해보자...
예를들어 비행기의 탑승자 명단을 알고싶다면 비행기의 객체를 생성해서 비행기의 멤버함수
showpeople() 이라는 함수를 호출해서 비행기의 탑승자 명단을 알수 있을것이다.
(여기서 이런함수가 있다고 가정하자)
그럼 버스라는 클래스를 생각해보자. 버스의 탑승자 명단을 알고싶으면 버스의 객체를 생성해서
버스의 멤버함수 showpeople() 함수를 호출하면 될듯하다.
이런 탈것에 관련된 클래스들이 점점 추가되어 많아지고 100개 1000개가 넘어간다 생각해보자.
우리는 이런 프로젝트를 진행하던중, showpeople()의 기능을 바꾸기로 결정한다.
단순히 탑승인원을 표시하는 것 보다는 성별별로 나누어 표시하기로 말이다.
또한 여객기 클래스만 특별하게 탑승인원의 나이별로 데이터를 세분해서 출력해주길 원한다면
더 나아가 각 탈것 클래스들마다 기능들이 조금씩 추가된다고 가정하자.
콘솔 출력형식이 입력을 받은 탈것에 대한 데이터에 대한 데이터를 보여주는 것이라 하면,
입력 형식에 따른 데이터 출력 포맷도 달라지게 만들어야한다.
그러기 위해서는 if 나 switch 를 통한 다중 분기를 통해 각 경우마다 입력 클래스객체에따라서 showpeople()함수를 호출해야할 것이다.
비슷한 기능을 하는 showpeople()지만 약간의 차이들로 인해 이런수고를 해야한다.
또한 이런 데이터들을 일괄적으로 다루어야하는데? 객체들을 생성은 해놨지만, 어떤식으로 묶어야 하는가?
이래서 상속이 필요한것이다.
모두 탈것이지만 각각 객체를 생성한후 일괄적으로 다루기 힘들다.
그래서 위에 선언한것같이 A *a=new B(); 이런 선언이 필요한것이다.
A클래스의 포인터타입인 a로 B객체를 다루겠다는 것.
좀더 나아가 a를 포인터 배열로 선언하게되면 A하위 객체들을 일괄적으로 다룰 수 있게된다.
a[i].peopleshow()은 a[i]가 비행기가 되든 버스가 되든 알아서 자기만의 포맷으로 출력할 수 있게된다.
하지만 위에서 A클래스의 함수만 호출된다고 말했었다.
이런 문제를 해결하기 위해서 해당함수를 virtual 로 선언을 하게되면 각 탈것에 맞는 showpeople()이 호출되게 된다.
virtual... 이문제는 다음에 알아보도록하자.
2013/06/04 - [프로그래밍/c++] - 가상함수 virtual 키워드 C++
2014/01/04 - [프로그래밍/c++] - 가상함수(virtual),vptr,vtable C++