2009.06.21 17:34 책 정리/Modern C++ Design

C++의 공부는 무척 어렵다. 관련 라이브러리가 무엇이 있는지 알아가는데만도 많은 시간이 필요할 뿐 아니라, 언어 자체가 지원하는 코딩 방법도 무척 맣기 때문이다. 그러다가 Template MetaProgramming 을 접하게 되면, 이게 C++ 이야 스크립트 언어야 라는 혼란에 빠지며, 한계에 부딛치게 된다. Template 관련 책부터 봐야겠다 싶어서 다음 3개의 책을 구매 했었는데

1. C++ Template MetaProgramming
2. Modern C++ Design
3. C++ Template

이 중에 1번과, 2번은 정말 상상을 초월할 정도로 어려운 책이다. 한 문구를 몇번이나 반복해서 읽어야지만, 70%는 포기하고, 20%는 아리송하게 넘기고, 9%는 그러겠지 하며, 단 1% 이해만 될 정도니 말이다. 3번 책으로 어느정도 익숙해 지자, 드디어 Modern C++ Design 책을 펴 보게 되었다. 자 그럼 시작이로다!

1장, 단위 전략 기반의 클래스 디자인
긴장을 유도하는 단어로 시작된다. .. 마치 군대 고참이 나를 갈굴때 처럼 긴장이 조성 된다. 원어를 보니 다음과 같이 되어 있었다.

policy-based class design

이 뜻은 작은 단위의 클래스들을 결합하여 복합된 새로운 클래스를 디자인하는 방법론이라 한다. 오오 정말 지나가던 사람과 마주치면 한번 이야기 해 볼 법한 주제이지 않을 수 없다. 설계하는 사람, 벽돌 쌓는 사람, 시멘트 바르는 사람, 전기 공사 하는 사람, 부실공사인지 체크 하는 사람, 자제 관리하는 사람, 상하수도 까는 사람들을 이용하여 건물을 짓는데, 이러한 독립된 각기 다른 사람들을 만드는 방법을 말하는거 같다.

1.1 이 책은 무슨 내용을 담고 있는가?
소프트웨어 디자인(각 패턴들만 생각해봐도 잠이 올 정도이다.)은 무척 다양하고 광범위 하므로, 안드레 알렉산드레스쿠가 기존의 디자인 문제점과 디자인 하는 방법을 담고 있으며, 이 방법을 통해 무척 많은 라이브러리가 담겨져 있다.

1.2 그렇다면 기존의 Do-It-ALL 인터페이스는 무엇이 문제인가?
모든 기능이 한 클래스에 있기 때문에 유지보수가 무척 어려운 문제가 있으며, 이것은 문법적으로만 올바른 코드일 뿐, 의미상으로도 여러가지 개념들이 합쳐 있어, 매우 복잡한 상태를 유지한다. 바로 이것들이 문제이다.

문제를 어떻게 해결 해야 하는가?
이런 문제들을 해결하기 위하여 if문으로 자체적인 제한 조건을 주기 보다, 디자인의 개선을 통하여 디자인 자체가 개념을 분리하고 제한도록 유도하여 해결해야 한다. 이렇게 말하니까 .. 뭐 어렵네, 쉽게 말해서, 클래스의 private 속성을 지닌 값을 클래스 내부에서만 사용 될 수 있듯이 클래스 자체가 이러한 제한을 갖도록 만들어 해결해야 한다는 것이다. 이 말을 여기서 이해하지 못해도 된다. 왜냐하면 밑에 코드가 있기 때문이다.

1.3 그런데 다중 상속을 이용하면 개념을 분리 할 수 있지 않을까?
다중 상속 이야기가 나온 이유는, C++에서 가장 많이 쓰이는 디자인 방법이기 때문이다. 이 방법은 많이 쓰이는 것에 비하여, 디자인 개선을 효과적으로 높여주지는 못한다.  다음 문단은 그 문제들을 정리해 둔 것이다.

구조적인 문제 : 다중 상속은 C++에서 단순하게 포개놓은 것일 뿐, 각 동작들이 서로 조율해주지는 못한다. 예를 들어, 여러개를 상속하면, 상속된 클래스는 이 녀석들을 조율해 주기 위해서 코드를 다시 짜야 한다. 이것은 안드레 알렉산드레스쿠가 제시하는 "스스로의 제한 조건"에서 벗어나게 된다.

자료형 파악의 문제 : 상속을 사용하다 보면, 마지막으로 상속한 클래스가 어떤 변수, 어떤 멤버 함수들을 가지고 있는지 파악하기가 무척 힘들어 진다. 더군다나 DeepCopy라는 기반 클래스를 사용 할 때, DeepCopy는 자신을 상속한 클래스도 모른체 DeepCopy 를 사용하고 DeepCopy 포인터를 얻어야 하는 곤란한 경우가 생긴다. 이 곤란한 경우는 DeepCopy 포인터가 현재 어떤 객체를 가리키고 있는지 코드의 문맥만으로 이해해야 한다는 것이다.

상태 처리의 문제 : 다중 상속에서 말하는 상태란, 바로 내부 변수를 말한다. 이 내부 변수가 다중 상속을 겪게 되면 다이아몬드 형태의 상속을 겪게 될 가능성이 무척이나 크다.  디이아몬드 형태의 상속의 문제는 다중 상속되는 클래스의 내부 변수에 접근하려 할 때, 발생 된다.

결국, 다중 상속은 코드를 합치는 C++의 유일한 디자인 이지만, "단순히 포개기만 한다, 자료형을 파악 할 수 없다, 상태처리가 곤란하다"라는 문제를 가지고 있다.

1.4 그렇다면, 대안이 있는가?
바로 템플릿이다. 클래스 템플릿을 사용하면, 일단은 자료형을 손쉽게 파악 할 수 있으며, 특정 상태(특수화)를 손쉽게 제어 할 수 있다. 하지만 다음의 문제를 가지고 있다.

클래스 템플릿만으로는 아무것도 못하는 문제 : std::vector 만 보더라도 이해가 될 수 있다. 왜냐하면 std::vector 로는 아무것도 못한다. 최소한 std::vector<int> 라고 해 주어야 하낟.

멤버 함수의 특화 확장성이 제한 되는 문제 : 한개의 템플릿 파라미터를 가진 클래스라면 가능하지만, 템플릿 파라미터가 여러개인 클래스 템플릿이라면, 멤버 함수의 부분 특화가 불가능하다. 이건 한번 해 보면 확 알 수 있을 것이다.

멤버함수에 다중으로 기본값을 제공 할 수 없는 문제 : .. 사실 이건 상속을 사용하더라도 기본값 제공을 통일하는게 좋지 않겠냐는 스콧 마이어스의 말이 생각난다.  문제라기 보다는 제한이라고 생각 된다.

템플릿이 아무리 대안이라 해도, 단점이 이렇게나 있으니, 왠지 써먹으면 오히려, 직장 짤리지 않을까 한다. ㅋ 그런데 재미있게도, 템플릿의 단점은 클래스의 장점이 보안해 주고 템플릿의 단점은 상속의 장점이 보완해 준다.

그러므로 지금까지 내용을 다시 정리하자면, policy-based class design(단위 전력 클래스 설계법)은 "템플릿과 상속"을 혼합을 함으로써 완성이 되는 것이다.


1.5 그렇다면, 어떻게 혼합 할 수 있는가?
한가지 예를 들고, 그 것을 해결하는 과정으로 보여 주겠다. 클래스 내부에서 객체를 생성 하려고 한다. 그렇다면 일반적으로 다음과 같을 것이다.

하지만 사용하다보니까, 클래스가 아닌 것을 생성하는 경우가 더 많더라. 그래서 할당시 속도를 높이기 위해서 malloc 을 사용 하고 싶어 다음과 같이 만들었다.

이제 나는 행복함을 느끼며 두 다리를 쭉 뻣고 잠을 자려 하는데, "아.. 제길 클래스를 할당 할 때도 있는데, 클래스를 두개 만들어서 따로 쓰기도 참 껄끄럽네.." 라는 생각을 떨쳐 버릴 수가 없을 것이다

이때 안드레 알렉산드레스쿠(... 이름 한번 멋지단 말이야. 이제부터 렛쿠라 불러야지)가 제시했던 "템플릿과 상속"을 이용한 policy-based class design을 이용하여, 템플릿으론 손쉽게 create를 결정하고, 상속을 이용하여, 코드를 결합 해보자,

어떠한가? 템플릿으로 코드를 생산하고, 클래스로 사용하는 저 상호 보완적인 모습이... 용어를 정리하면 여기서 정책을 강요당하는 클래스, 즉, 상속받은 클래스를 host class 라 부르고, 정책이 되는 클래스 즉, 상속을 주는 클래스를 policy-based class 라 한다.

책에선 템플릿 템플릿 파라미터를 활용하여 처리 하는데, 위의 예와 같지 않아 생략했다. 같지 않는 것은 나는 할당이라는 행위와 대상이라는 변수가 계속 변해야 하지만, 책에선 할당일는 행위만 변하기에, 템플릿 템플릿 파라미터가 더 깔끔한 것이다.

지금까지 정리한 내용으로 실제 코딩을 하려 한다면, 몇가지 생각할 것이 있다. 1. policy-based class의 소멸자 정의 방법, 인터페이스 보강 방법, 그리고 클래스를 단위전략으로 분리해 내는 방법 이다.

1. 소멸자 정의 방법
일반적으로 단위 전략 클래스의 경우 멤버 변수를 가지고 있지 않는다. 왜냐하면 상속 체계를 따라가다 보면, 다이아몬드를 하기 쉽기 때문이기도 하고, 단위 전략 클래스 특성상 멤버 변수를 갖을일이 없기 때문이기도 하다. 어찌 되었던 소멸자를 정의 할 때 다음과 같은 선택이 있다.

하나, 가상 소멸자로 만든다.
하지만 이 방법은 좀 껄끄럽다. 왜냐하면 함수가 필요해서 상속 했을 뿐인데, 모든 인스턴스 객체에 대해서 가상 테이블이 생기는 것은 원치 않기 때문이다.

하나, protected 소멸자로 만든다.
바로 이 방법이 제일 좋다. 왜냐하면, 정책 기반 클래스의 포인터로 사용한다 하더라도 delete를 할 수 없을 뿐더러, 가상 테이블이 생기지 않아, 오버헤드가 일어나지 않기 때문이다.

이것으로 소멸자 정의 방법을 정리하고, 다음으로 인터페이스 보강 방법에 대해서 들 수 있따.

2. 인터페이스 보강 방법
여기서 말하는 인터페이스 보강 방법은, 정책 기반 클래스의 인터페이스가 추가 된다면, 호스트 클래스의 인터페이스 역시 추가 될 요지가 있는데, 그 추가 방법을 말하는 것이다.
하지만 이 것은 방법이라고 까지 말 할 필요가 없다면, 왜냐하면 C++에서 지원해 주고 있는 요소들이 알아서, 불완전한 구체화를 통해서 처리해 주기 때문이다. ... 이것은 클래스 템플릿의 경우 사용하지 않는 멤버 함수는 인스턴스화 되지 않는 규칙에 의해서 만들어 진다. 그러므로 필요한 기능을 추가하고 사용하면 알아서 컴파일 타임 에러 날 건 나고 쓰지 않는건 그냥 지나가게 된다.

다음으로는 가장 중요한 "클래스를 단위로 분리 하는 방법" 일 것이다..

3.클래스를 단위로 분리 하는 방법
정형화된 방법은 없지만 단 한가지 절대적인게 있다. "독립 단위로 인정 될 수 있다면 뺄 것, 그렇지 않다면 빼지 말것" 이것만 지킨다면, 큰 문제 없이 사용 할 수 있을 것이라고 나는 생각한다.


여담
책에서 더 자세한 내용이 있으니, 직접 보는게 더 좋다. 책에선 스마트 포인터를 이용하여 각 단위가 무엇이 있을 수 있는지, 왜 복사 생성자가 템플릿일 수 밖에 없는지 등을 이해할 수 있게 정리되어 있다. 안 보고 놓친다면 아깝지 않을 수 없지 않겠는가? ㅋㅋ

1장에선 디자인 이야기 였다면, 2장에선 테크닉 이야기를 하고 한다. 알드레 알렉산드레스쿠 당신은 선구자요. ㅋ


저작자 표시
신고
posted by 농사를 짓는 게임 프로그래머 최익필

댓글을 달아 주세요

  1. Favicon of http://blog.naver.com/rkawk01 감자 2009.06.25 11:32 신고  Addr  Edit/Del  Reply

    와.. 정리 잘해주셨네요
    혼자 책읽을땐 이해 되는듯 하면서도 아리송했는데
    이 글을 읽으니 좀 더 잘 이해되네요.

    개인적으로 이 장에서 가장 신선했던 내용은
    인터페이스 보강에 관한거 였는데요
    사실 문법적으로 맞지 않는 함수호출이 있음에도
    사용되지 않으면 컴파일 되지 않으니까 문제없다라고 하더군요
    처음엔 납득하기 어려웠는데 꼭 그렇게 사고할 필요 없겠더라고요 ㅋ
    아무튼 정리해주신 글 잘읽었습니다.

  2. 정윤성 2016.09.17 01:18 신고  Addr  Edit/Del  Reply

    6개월도안 공부한 학생입니다. . 그런데 2009년이지만 7년이나 지난 지금에서도 새로운 패러다임이라고 생각할만한 글이 많네요 잘보고 가요~