상속을 설명하기 위한 class 들은 다음과 같습니다.
Class | Data | Method |
---|---|---|
StudentInfo | name, address | print 'name' and 'address' |
UniversityStudent | name, address, university_name | print 'name', 'address' and 'university_name' |
UniversityStudent가 StudentInfo를 상속하고, StudentInfo로부터 UniversityStudent는 name, address를 상속받고 자신만의 데이터인 university_name을 갖습니다. method는 StudentInfo에서 상속받지만 그대로 상속받지 않고 약간 변형합니다(Method override).
이 두 클래스를 GObject로 구현해 보겠습니다.
우선, Class를 구현하기 위한 구조체는 다음과 같습니다.
UniversityStudent* us;
us->parent.parent.g_type_instance.g_class->g_type
((GTypeInstance*)us)->g_class->g_type
위 코드는 GObject에서 어떻게 class구조에 대한 pointer를 가져오는지 보여주고 있습니다. C 구조체의 첫번째 필드는 항상 그 구조체의 첫번째 byte부터 저장되는데, 이 속성을 이용해서 우리는 UniversityStudent의 class 정보를 쉽게 가져올 수 있는 것입니다.
StudentInfo의 class 구현은 첫번째 GObject 설명에서 이미 다루었고, 여기서는 UniversityStudent class를 중심으로 설명하겠습니다.
UniversityStudent도 StudentInfo와 마찬가지로 university_student_base_init(), university_student_class_init(), university_student_init() 함수를 정의하고 g_type_register_static 함수로 type 정보를 등록합니다(university_student_get_type).
university_student_get_property(), university_student_set_property() 함수를 만들어 private data인 university_name을 읽고 쓸수 있도록 합니다.
university_student_finalize() 함수를 만들어서 UniversityStudent 객체가 파괴될 때 memory를 정리하는데, 이 함수에서 StudentInfo의 finalize 함수를 다음과 같이 호출합니다.
line 92에서 finalize() call을 통해서 Superclass의 finalize 함수를 실행합니다. 다음은 UniversityStudent의 class 초기화 함수입니다.
line 117에서 university_student_parent_class를 정의합니다.
line 126에서 GObject의 finalize 함수를 override 합니다.
line 128에서 StudentInfo Class의 print 함수를 university_student_print 함수로 override 합니다.
line 138에서 property를 등록하는데, property 한 개를 등록할 때에는 g_object_class_install_property를 사용하고, 여러 개를 한꺼번에 등록할 때에는 g_object_class_install_properties를 사용합니다(GObject property 설명 참조).
line 120에서 class pointer 들을 출력하는 부분이 있습니다. 실행 결과가 어떻게 될까요? 한번 생각해 보세요. 결과는 잠시 후에 설명합니다. 다음은 instance 초기화 함수입니다.
line 150에서 UniversityStudent 포인터로부터 class 구조체 포인터를 얻고, 그 값을 출력하고 있습니다. 비교를 위해서 parent class의 포인터(university_student_parent_class)도 출력합니다.
상속을 test 하기 위한 함수입니다. line 104에서 UnversityStudent type으로 GObject를 생성합니다. line 108 ~ 111까지 Instance 정보와 Class 정보를 얻기 위한 pointers를 선언합니다. uobj1은 UNIVERSITY_TYPE_STUDENT type 입니다. 따라서 line 114는 university_student_print 함수를 호출한 것과 같습니다. 또한 line 125도 uobj1의 StudentInfoClass 포인터를 얻어서 print 함수를 호출했기 때문에 line 114와 같은 함수를 호출한 것입니다.
line 118, 119에서 uobj1을 StudentInfo와 UniversityStudent type으로 각각 cast 합니다. C 문법으로 보면 STUDENT_INFO와 UNIVERSITY_STUDENT는 우리가 정의했던 macro이지만 GObject의 cast라고 생각하면 편리합니다. 따라서 line 121의 결과는 si, us 포인터들이 같은 값을 나타냅니다.
line 129 ~ 132의 출력 결과는 모두 같습니다. 즉 uobj1에 대한 class 포인터를 얻는 방법이 여러 가지라는 것을 나타냅니다. 특히 line 132의 구문은 우리가 앞에서 봤던 GObject에서 Class 정보를 얻는 방법인데, 이것은 설명을 위해 추가한 것으로 보통은 이렇게 하지는 않습니다. line 131처럼, 미리 정의한 macro를 사용하지요.
그럼 우리가 override된 함수가 아닌 부모 class의 함수만 호출하고 싶다면 어떻게 할까요? line 139가 그 방법을 알려주고 있습니다. 부모 class의 pointer를 얻고 싶을 때에는 g_type_class_peek_parent 함수를 사용합니다.
line 142에서 uobj2를 생성하고, line 145에서 그 값을 출력하고, line 147, 148에서 생성되었던 GObject들을 해제합니다. g_object_unref의 결과 finalize 함수가 호출되고, memory는 안전하게 헤제되는 것입니다.
위 코드를 실행한 결과는 다음과 같습니다.
Start main() ...
student_info_base_init() called 1
student_info_class_init() called
gobj_class: 0x971ee30, klass:0x971ee30
student_info_parent_class: 0x971ed00
student_info_base_init() called 2
university_student_base_init() called
university_student_class_init() called
gobj_class: 0x971ef40, klass: 0x971ef40
university_student_parent_class: 0x971ee30
student_info_init() called
StudentInfoClass: 0x971ee30
student_info_parent_class: 0x971ed00
university_student_init() called
UniversityStudentClass: 0x971ef40
university_student_parent_class: 0x971ee30
university_student_print() called
student_info_print() called
Name : 김혜진
Address : 부산
University : 부산대
Instance pointers for uobj1:
STUDENT_INFO: 0x971f810, UNIVERSITY_STUDENT: 0x971f810
STUDENT_INFO_GET_CLASS(uobj1)->print
university_student_print() called
student_info_print() called
Name : 김혜진
Address : 부산
University : 부산대
Class pointers for uobj1:
STUDENT_INFO_GET_CLASS: 0x971ef40
UNIVERSITY_STUDENT_GET_CLASS: 0x971ef40
((GTypeInstance*)us)->g_class: 0x971ef40
university_student_parent_class for uobj1: 0x971ee30
STUDENT_INFO_GET_CLASS(g_type_class_peek_parent(usc))->print
student_info_print() called
Name : 김혜진
Address : 부산
student_info_init() called
StudentInfoClass: 0x971ee30
student_info_parent_class: 0x971ed00
university_student_init() called
UniversityStudentClass: 0x971ef40
university_student_parent_class: 0x971ee30
university_student_print() called
student_info_print() called
Name : 김수진
Address : 서울
University : 서울대
부산대 information is being deleted.
김혜진 information is being deleted.
서울대 information is being deleted.
김수진 information is being deleted.
line 118, 119에서 uobj1을 StudentInfo와 UniversityStudent type으로 각각 cast 합니다. C 문법으로 보면 STUDENT_INFO와 UNIVERSITY_STUDENT는 우리가 정의했던 macro이지만 GObject의 cast라고 생각하면 편리합니다. 따라서 line 121의 결과는 si, us 포인터들이 같은 값을 나타냅니다.
line 129 ~ 132의 출력 결과는 모두 같습니다. 즉 uobj1에 대한 class 포인터를 얻는 방법이 여러 가지라는 것을 나타냅니다. 특히 line 132의 구문은 우리가 앞에서 봤던 GObject에서 Class 정보를 얻는 방법인데, 이것은 설명을 위해 추가한 것으로 보통은 이렇게 하지는 않습니다. line 131처럼, 미리 정의한 macro를 사용하지요.
그럼 우리가 override된 함수가 아닌 부모 class의 함수만 호출하고 싶다면 어떻게 할까요? line 139가 그 방법을 알려주고 있습니다. 부모 class의 pointer를 얻고 싶을 때에는 g_type_class_peek_parent 함수를 사용합니다.
line 142에서 uobj2를 생성하고, line 145에서 그 값을 출력하고, line 147, 148에서 생성되었던 GObject들을 해제합니다. g_object_unref의 결과 finalize 함수가 호출되고, memory는 안전하게 헤제되는 것입니다.
위 코드를 실행한 결과는 다음과 같습니다.
Start main() ...
student_info_base_init() called 1
student_info_class_init() called
gobj_class: 0x971ee30, klass:0x971ee30
student_info_parent_class: 0x971ed00
student_info_base_init() called 2
university_student_base_init() called
university_student_class_init() called
gobj_class: 0x971ef40, klass: 0x971ef40
university_student_parent_class: 0x971ee30
student_info_init() called
StudentInfoClass: 0x971ee30
student_info_parent_class: 0x971ed00
university_student_init() called
UniversityStudentClass: 0x971ef40
university_student_parent_class: 0x971ee30
university_student_print() called
student_info_print() called
Name : 김혜진
Address : 부산
University : 부산대
Instance pointers for uobj1:
STUDENT_INFO: 0x971f810, UNIVERSITY_STUDENT: 0x971f810
STUDENT_INFO_GET_CLASS(uobj1)->print
university_student_print() called
student_info_print() called
Name : 김혜진
Address : 부산
University : 부산대
Class pointers for uobj1:
STUDENT_INFO_GET_CLASS: 0x971ef40
UNIVERSITY_STUDENT_GET_CLASS: 0x971ef40
((GTypeInstance*)us)->g_class: 0x971ef40
university_student_parent_class for uobj1: 0x971ee30
STUDENT_INFO_GET_CLASS(g_type_class_peek_parent(usc))->print
student_info_print() called
Name : 김혜진
Address : 부산
student_info_init() called
StudentInfoClass: 0x971ee30
student_info_parent_class: 0x971ed00
university_student_init() called
UniversityStudentClass: 0x971ef40
university_student_parent_class: 0x971ee30
university_student_print() called
student_info_print() called
Name : 김수진
Address : 서울
University : 서울대
부산대 information is being deleted.
김혜진 information is being deleted.
서울대 information is being deleted.
김수진 information is being deleted.
이 출력은 첫번째 글의 StudentInfo에 대한 설명에 나온 코드와 조금 다른데, 그 이유는 제가 출력문만 보기 편하게 조금 손질했기 때문입니다. 또한 전체 코드는 여기에 있습니다.
university_student_class_init 함수의 출력을 보면 university_student_parent_class에서 부모의 class pointer를 출력하고 있는데, 이 값이 StudentInfoClass의 출력 값과 같습니다. 즉 university_student_parent_classs는 정확하게 부모의 class pointer 값을 가지고 있음을 알 수 있습니다.
student_info_base_init 함수의 호출 순서를 살펴보면 이 함수가 언제 쓰이는지 알 수 있습니다. 즉 student_info_class_init가 호출되기 전에 한번, university_student_base_init가 호출되기 전에 또 한번 호출되었는데, 이는 student_info_base_init 함수가 class hierachy에서 자신을 포함한 모든 자식 class가 만들어질 때마다 호출됨을 의미합니다.
uobj2를 생성할 때에는 class 생성 함수들이 호출되지 않는 것도 볼 수 있습니다. 이는 object-oriented programming의 특성으로, class는 한번만 생성되고 instance는 여러 개가 생성되므로 당연한 결과 입니다.
이상으로 GObject 상속에 대한 내용을 마칩니다. 다음에는 interface에 대한 내용을 다룰 예정인데, 글을 올리는 것이 생각보다 많은 노력이 들어가네요. 게다가 Algorithm 연구 등 개인적으로 공부하는 것도 많아서 글을 올리는 속도가 굉장히 느립니다. 그렇지만 GObject 뿐만 아니라 GLib API까지 다루어보고 싶고, 아무리 오래 걸리더라도 그 목표를 달성하고 싶습니다.