Extreme C 5~8 장 중, 6장/7장/8장 내용을 초록
oop 는 최신 oop language 들에만 해당되는 내용이고, old 한 언어, C 언어에겐 해당사항이
없다고 생각될수 있으나, 사실 oop 는 언어가 아닌, 사고하는 방식을 뜻한다.
먼저 oop 는 2가지 방식으로 구현될수 있고, 전자는 ruby, 후자엔 c++ 와 java 가 있다.
c 언어는 oop 언어와 기계어 사이에 있는 언어라, oop 언어가 아니지만
몇가지 생각체계를 갖고 작성할 경우,
oop 스럽게 짤수 있다. (user 추가 type, UDT 를 'class' 처럼 작성)
oop 스럽게 짠 다양한 c 기반 opensource 들이 있기 때문이다.
그들의 성공이 oop 스러운 c 가 내재한 강력함을 암시하고 있다.
1) type 을 file 별로 관리한다. 구현(.c) 과 public interface(.h) 로 나눈다.
2) behavior 함수의 첫번째 인자로 object 의 opaque pointer 를 받는다.
attribute 와 behavior 를 묶는데 사용되는 도구를 oop 언어에서 제공받으면
좀더 편하게 작성하겠지만, 그 도구들이 없이도 c 에서도 가능하다.
public interface 에 attribute 를 공개하게 될땐, 항상 backward compatibility 를 고민해야 한다.
왜냐면 공개된 attribute 는 다른 구현들이 쓸수 있다는 뜻이므로, 변경이 어렵게 된다는 걸 뜻하기 때문이다.
type 사이는 관계를 to-have 와 to-be 로 구분할수 있다. 여기선 먼저 to-have 관계를 다루겠다.
to-have 관계는 composition 과 aggregation 둘로 구분이 될수 있다.
이 둘을 구분할땐 object 관점에서 보면 확연히 드러난다.
한 object 가 다른 type 의 opaque pointer 를 가지고 있다는 것은,
다른 type object 의 life cycle 을 '어떻게 다룰지' 에 대한 고민으로 이어지게 된다.
먼저 object 가 다른 object 를 '소유' 한다고 보면, 그 관계는 composition 이 된다.
object 는 소유한 object 의 life cycle 을 관리할 "책임" 을 갖게 된다.
따라서 object 가 만들어질때, 자신이 소유한 object 까지 같이 만들고,
object 가 사라질땐, 그전에 자신이 소유한 object 까지 같이 제거한다.
composition 관계에서 빌드는 다음과 같은 순으로 진행될수 있다.
car 가 engine 을 소유하는 composition 관계를 예로 들면,
1) car 의 구현이 engine 의 public interface 를 사용
2) engine 의 public interface 는 engine 의 구현을 필요로 함
--> 결국 car 와 engine 의 구현이 최종 바이너리 링크 타임에 합쳐지게 됨
다음으로 object 가 다른 object 를 소유하지 않고, '일시적으로' 사용만 한다고 보면,
그 관계는 aggregation 이 된다.
따라서 일시적으로 사용되고 있는 object 의 life cycle 관리는 제3의 함수 (ex. main) 에게
주어지게 된다.
aggregation 관계에선 opaque pointer 가 NULL 일수도 있으니, 사용하는 쪽에선 주의가 필요
object 간의 관계는 '일시적으로' aggregation 이지만, type 측면에서 보면 '영구적으로' aggregation 관계가 된다.
inheritance 는 to-be 관계로, is-a 로 표현할수 있는 두 type 을 뜻한다.
inheritance 는 또 "extension relationship" 으로 불리기도 하는데,
이는 type 에 새로운 attribute 를 '추가' 하는것만 가능하기 때문이다.
1) parent type 의 public interface 를 사용하지 않는 방법
2) public interface 를 사용하지 않는 방법
먼저 첫번째 방법은 child type 내에 parent type 'variable' 을 넣는게 핵심이다.
문제는 parent type 이 incomplete data type, 즉 정의를 모르므로, type 크기를 알수 없어
메모리 할당이 불가하다.
따라서, parent type 크기를 알려주기 위해, chlid type 구현에서 include 할수 있는
private header 를 작성하게 된다.
또한, 이때 parent type variable 은 반드시 첫번째 field 여야 한다.
그래야 두 opaque type 포인터 (parent, child) 가 동일 메모리 주소를 가리키게 되고,
덕분에 parent type 의 behavior 를 child 에서도 재사용이 가능하게 된다.
두번째 방법은 public interface 를 사용하는 방법이다.
이 경우, parent type 수정시 child type 을 신경쓰지 않아도 되는 장점이 있으나,
(child 는 parent type 정의를 전혀 모르므로)
대신 pointer upcast 가 불가하므로, parent type 의 behavior 를 재사용하는게 불가하고,
별도의 interface 를 정의해서, parent public interface 의 wrapper 처럼 이용하게 된다.
두 방법 모두 child object 가 parent object 의 life cycle 을 관리하고 있으므로,
근본적으로 (intrinsically) composition 관계가 된다.
두 방법을 비교해보면,
첫번째 방법은, child 가 1명의 parent 만 가질수 있다는 단점이 있으나,
대신 parent 의 behavior 를 그대로 사용하고 싶을때 쓰면 용이하다는 장점이 있고,
두번쨰 방법은, parent behavior 를 쓰려면 child 쪽에 별도의 wrapping 함수들을
만들어야 한다는 단점이 있으나,
대신 child 가 n명의 parent 를 가질수 있고, parent 수정에 child 가 독립적이며,
parent 가 child 의 첫번째 field 여야 한다는 제약도 없다는 장점이 있다.
Polymorphism 은 기존 type 에 새로운 동작(morph) 를 추가하고 싶을때 사용하게 된다.
따라서 subtype 을 작성하게 되는데, subtype 의 동작을 parent type 에게 넘기기 위해,
parent type 내부에는 function pointer 를 담을 placeholder 를 정의하게 된다.
child object 은 초기화 시, parent object 도 초기화 하는데,
이 때 function pointer 를 자신의 함수로 "override" 하게 된다.
따라서 c에서 Polymorphism 을 쓰려면, upcast 가 가능한 첫번째 interitance 사용이 필요하다.
parent type 은 placeholder 에 있는 function pointer 를 호출하는 역활만 수행하게 된다.