2016년 3월 12일 토요일

[c++] 상속 1 - 이니셜라이저, protected

선 다음의 상속이 구현된 소스 코드를 보고 문제점이 무엇인지 알아 보자.  
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Person  
  5. {  
  6.     int age;  
  7.     char name[20];  
  8. public:  
  9.   
  10.     int GetAge() const {  
  11.         return age;  
  12.     }  
  13.     const char* GetName() const {  
  14.         return name;  
  15.     }  
  16.   
  17.     Person(int _age=1, char* _name="noname"){  
  18.         age=_age;  
  19.         strcpy(name, _name);  
  20.     }  
  21. };  
  22.   
  23. class Student: public Person  
  24. {  
  25.     char major[20]; //전공  
  26. public:  
  27.     Student(char* _major){  
  28.         strcpy(major, _major);  
  29.     }  
  30.     const char* GetMajor() const {  
  31.         return major;  
  32.     }  
  33.     void ShowData() const {  
  34.         cout<<"이름: "<<GetName()<<endl;  
  35.         cout<<"나이: "<<GetAge()<<endl;  
  36.         cout<<"전공: "<<GetMajor()<<endl;  
  37.     }  
  38. };  
  39.   
  40. int main(void)  
  41. {  
  42.     Student Kim("computer");  
  43.     Kim.ShowData();  
  44.   
  45.     return 0;  
  46. };  
 위의 소스 코드 문제점을 보자면 main에서 객체 생성을 하는 Student Kim 객체선언 하는것을 보아 이름이 Kim으로 예상할 수 있겠다. 하지만 여기서 나이와 전공은 우리가 원하는 형태로 초기화 하지 못한다는 점이 바로 문제점이다. 즉, Student 클래스 객체가 생성될때, 자신의 멤버는 생성자내에서 초기화 하고 있지만, Person 클래스의 멤버는 default 값으로 초기화 되고 있는게 문제라고 할 수 있다. 
 잠깐 소스를 살펴 보자면, showdata 함수 내에서 GetName이라는 함수를 호출 하고 있는데, Student 클래스내에서는 GetName이라는 함수가 없지만, 이것을 호출 할 수 있는 이유는 Student 클래스가 Person 클래스를 상속하고 있기 때문에 Person 에 GetName 이라는 함수가 있어서 이런 호출이 가능한 것이다. (상속의 특성이라 할 수 있다.)

 문제로 돌아와서, Student 클래스에 Person의 멤버 변수들도 Student 클래스의 멤버로 상속되어 지니까 아예 Student 클래스를 정의 할때, 인자값으로 나이와 이름을 초기화 해서 Default로 초기화 되는것을 개선해 보자는 것이다.  
  1. Student (int _age, char* _name , char * _major)  
  2. {  
  3.     age = _age;  
  4.     strcpy(name, _name);  
  5.     strcpy(major, _major);  
  6. }  
 위와 같이 Student 생성자에서 age와 name을 초기화 해주면 될것이다. 하지만 Person 멤버들이 private 으로 선언되어 있으니 위와 같이 코딩을 하면 컴파일 에러가 날것이다. 왜 이런 에러가 나는 것일까? 비록 Person 클래스의 멤버는 Student 클래스에 의해 상속 되어 지지만, Person 클래스의 멤버들이 private이기 때문에 Person 클래스 내에서만 접근 가능 하기 때문이다. 
 그렇다고 이 문제를 해결하기 위해 public으로 멤버 변수들을 선언하면 문제는 해결되겠지만, 객체지향의 정보은닉이 무너지는 결과를 낳을 것이다. 그래서 필요한게 바로 멤버 이니셜라이저(member initializer)이다. (또는 초기화 리스트)

  1. Student(int _age, char* _name, char* _major)  
  2.     : Person(_age, _name)  
  3. {  
  4.     strcpy(major, _major);  
  5. }  
 보통 멤버 이니셜라이저에서는 멤버 변수의 초기화를 이루기 위해 멤버변수 이름이 오지만, 여기에서는 클래스 이름이 나왔다. 이 의미는 _age와 _name 이 두개의 인자값을 받을 수 있는 생성자를 호출하라는 의미로 바뀐다. 이제 완성된 코드로 실행해보자. 이제 원하는 대로 출력이 되는 것을 알 수 있다. 
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Person  
  5. {  
  6.     int age;  
  7.     char name[20];  
  8. public:  
  9.   
  10.     int GetAge() const {  
  11.         return age;  
  12.     }  
  13.     const char* GetName() const {  
  14.         return name;  
  15.     }  
  16.   
  17.     Person(int _age=1, char* _name="noname"){  
  18.         age=_age;  
  19.         strcpy(name, _name);  
  20.     }  
  21. };  
  22.   
  23. class Student: public Person  
  24. {  
  25.     char major[20]; //전공  
  26. public:  
  27.     Student(int _age, char* _name, char* _major)  
  28.         : Person(_age, _name)  
  29.     {  
  30.         strcpy(major, _major);  
  31.     }  
  32.     const char* GetMajor() const {  
  33.         return major;  
  34.     }  
  35.     void ShowData() const {  
  36.         cout<<"이름: "<<GetName()<<endl;  
  37.         cout<<"나이: "<<GetAge()<<endl;  
  38.         cout<<"전공: "<<GetMajor()<<endl;  
  39.     }  
  40. };  
  41.   
  42. int main(void)  
  43. {  
  44.     Student Pac(19, "Pacs.tistory.com""computer");  
  45.     Pac.ShowData();  
  46.   
  47.     return 0;  
  48. };  

protected는 접근제어 키워드 중 하나로, 정보은닉 포스팅에서 이미 언급한 바가 있는데, protected는 private과 기능이 완전히 동일 하지만, 이것이 상속관계에서 쓰일 때는 파생 클래스가 기본 클래스로의 접근이 가능해진다. 
  1. class Person  
  2. {  
  3. protected:  
  4.     int age;  
  5.     char name[20];  
  6. public:  
  7.       .................  
  8. };  
 사용할 때는 위와 같이 private 대신에 protected를 써주면 되겠다. 하지만 protected를 쓴다고 해도 멤버 이니셜라이저를 쓰는것이 좋은 구조이다. 만약 멤버 변수의 이름이 변경되어 지는 경우가 생겼다고 한다면, 이 바뀐 이름들을 자신 클래스는 물론이고, 상속을 하고 있는 클래스에서도 이름을 바꿔야 하는 문제가 생긴다.  단순히 이름을 바꾸면 된다고 생각하기 쉽지만, 한클래스의 변경은 다른 클래스의 변경을 유발 시켰기 때문에 심각한 문제라고 볼 수 있다. 
  
 ※ Derived class 생성자 내에서는 Derived class의 멤버 변수만 직접적으로 초기화 시켜 주고 Base class 멤버는 Base class의 생성자를 통한 초기화가 가장 좋은 방법이라고 말할 수 있다. 

댓글 없음:

댓글 쓰기

[Effective C++] 항목 30 : 인라인 함수는 미주알고주알 따져서 이해해 두자.

인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별 최적화를 걸기가 용이해집니다. 인라인 함수의 아이디어는  함수 호출문을 그 함수의 본문으로 바꿔치기하자는 것  남발했다가는 코드의 크기가 커질 게 뻔하다. 인라인 함수로 부풀려진 ...