Post

[Effective C++] 9. 그 밖의 이야기들 [1/2]

이 글은 제 개인적인 공부를 위해 작성한 글입니다. 틀린 내용이 있을 수 있고, 피드백은 환영합니다.

[Effective C++] 9. 그 밖의 이야기들 [1/2]

개요


‘그 밖의 이야기들’ 장의 항목 개수는 3개뿐이지만, 정말 중요하다. 이 장은 최소한 소프트웨어가 제대로 동작하기를 원한다면 컴파일러 경고를 가볍게 넘기지 말 것을 강조하는 항목으로 시작한다. 그 다음에는 표준 C++ 라이브러리의 구성요소를 전반적으로 조명하는 항목이 나오는데, 새로이 도입된 TR1의 주요 기능들도 여기서 다루어진다. 마지막으로, 셋째 항목에서는 부스트를 집중 조명한다. 약간의 논란의 여지가 있겠으나, 특정 용도나 개발사에 치우지지 않은 성격의 C++ 관련 단체이자 웹사이트로서 사실상 가장 중요한 곳이라고 할 수 있다.


항목 53 : 컴파일러 경고를 지나치지 말자


아침 식사 거르듯이 컴파일러 경고 보기를 돌같이 하는 프로그래머들을 자주 보곤 한다. 진짜로 심각한 문제라면 컴파일러 경고가 아니라 에러가 나올 테지, 그렇게 생각할 것이다. 다른 언어에서는 이렇게 생각한다고 해서 큰 문제가 생기지는 않을지도 모른다. 하지만 C++의 경우에는 우리가 저질러 놓은 코드가 어떤 것인가에 대해 컴파일러 제작자가 우리보다 더 잘 이해하고 있기를 바라는 것 자체가 큰 도박이다. 예를 하나 들자.

누구나 한 번쯤은 저질러 봤을 법한 실수 하나를 아래에 써 보자.

1
2
3
4
5
6
7
8
9
10
11
class B
{
public:
    virtual void f() const;
};

class D : public B
{
public:
    virtual void f();
};

가상 함수인 B::f를 D::f에서 재정의(redefine, 오버라이드)하겠다는 의도인데, 여기에 실수가 숨어 있다. B 클래스의 f는 상수 멤버 함수이지만, D 클래스의 f는 괄호 뒤에 const가 붙어 있지 않다. 필자가 알고 있는 컴파일러 중 하나를 써서 이 코드를 돌리면 다음과 같은 경고가 나온다.

1
warning: D::f() hides virtual B::f()

아직 초보 프로그래머들 사이엔 이 경고 메세지를 보고 당연하다는 듯 반응하는 사람도 많다. 지금 컴파일러는 B에서 선언된 f가 D에서 재선언된 것이 아니라 아예 가려졌다는 사실을 외치고 있다 (어째서 그런 것인지는 항목 33을 다시 보자). 이 경고를 무시하고 지나갔다가는 프로그램이 십중팔구 이상하게 동작될 것임은 물론, 디버깅을 하면서 밤을 지세울 수 있다.

우리가 주로 사용해 온 특정 컴파일러에서 내주는 경고 메세지들에 어느 정도 익숙해지고 나면, 이 외에 다른 메세지들도 어떤 뜻으로 나온 건지를 슬슬 이해하는 수준에 오게 된다(눈에 보이는 것과 실제 뜻이 너무너무 자주 다르다는 게 문제다). 이러한 경험이 웬만큼 쌓이면, 어떤 경고는 넘어가도 되고 어떤 경고는 없애 주어야 하는지를 선택할 수 있는 공력도 쌓인다. 물론, 최고 경고 수준을 걸더라도 경고 메세지 없이 컴파일되는 코드를 작성하는 것이 일반적으로 더 좋은 실천법이라는 가르침도 무시하면 안 된다. 어떻게 했든 정말 중요한 점은 따로 있다. 우리 눈앞에 어떤 경고 메세지를 없애기 전에, 그 경고가 우리에게 알리려는 바를 정확히 이해해야 한다는 것이다.

컴파일러 경고는 제작사의 고유 선택에 따라 달라지는 것이기 때문에, 이것만큼은 태생적으로 구현별 의존 사항이란 사실을 잊지 말자. 그렇기 때문에 우리가 저지른 실수를 컴파일러가 대신 잡아주는 것만 믿고 아무 생각 없이 프로그래밍에 임하는 자세는 정말 좋지 않다. 이름을 가리는 위의 코드 예제만 봐도 그렇다. 어떤 다른 컴파일러의 경우에는 이 코드에 대해 찍 소리도 안 하는 경우도 있다.

컴파일러 경고를 쉽게 지나치지 말자. 우리의 컴파일러에서 지원하는 최고 경고 수준에도 경고 메세지를 내지 않고 컴파일되는 코드를 만드는 쪽에 전력을 다 하자.

컴파일러 경고에 너무 기대는 인생을 지양하자. 컴파일러마다 트집을 잡고 경고를 내는 부분들이 천차만별이기 때문이다. 지금 코드를 다른 컴파일러로 이식하면서 우리가 익숙해져 있는 경고 메세지가 온 데 간 데 없이 사라질 수도 있다.


항목 55 : Boo子有親! 부스트를 늘 여러분 가까이에 (Familiarize yourself with Boost)


이번 항목의 영어 제목은 Familiarize yourself with Boost이다. Boo子有親은 부자유친, 즉 아버지와 자식 사이에는 친함이 있어야 한다는 뜻인데 부스트와 프로그래머가 친해져야 한다는 의미로 말한 거 같다.

현장에 당장 투입할 수 있을 정도로 품질도 우수하고, 가격도 무료이며 내부 코드를 들여다볼 수 있는 오픈 소스이고, 게다가 어지간한 플랫폼과 컴파일러에서 모두 돌아가는 C++ 라이브러리를 찾고 잇다면 부스트가 대세이다. 현재 최고 수준의 라이브러리 설계 및 구현 실력으로 똘똘 뭉친, 열정과 재주를 겸비한 C++ 개발자 모임에 들어가고 싶다면 부스트가 대세이다. 현재의 C++가 갖게 될 미래를 미리 살짝 맛보고 싶다면 부스트가 대세이다.

부스트는 C++ 개발자들의 단체이자 무료 다운로드가 가능한 C++ 라이브러리 집합을 동시에 일컫는 고유명사이다. 웹사이트 주소 https://www.boost.org/ 에 들어가면 만날 수 잇다. 혹시라도 즐겨찾기에 이 주소가 안 달려 있다면 지금 당장 추가하자.

물론 C++만 다루는 개발자 단체 및 웹사이트는 부스트 말고도 전 지구상에 널리고 널렸다. 아주 많다. 하지만 부스트는 다른 곳에서는 절대로 따라올 수 없는 차별점이 두 개나 있다.

첫째, 부스트는 C++ 표준화 위원회와 밀접하고 영향력 있는 밀월관계를 유지하고 있는 곳으로 융리하다. 원래부터 위원회에 속해 있던 회원들 몇 명이 세운 곳이기도 하고, 그런 연유로 이름만 들어도 알 법한 유명한 분들이 부스트 및 현재 표준화 위원회에 양다리를 걸치고 있는 경우가 정말 많다. 게다가, 부스트가 세워진 이래로 꾸준히 지켜왔던 활동목표들 중 하나가 표준 C++에 추가될 수 있는 기능들의 시험 장소로 이 한 몸 기꺼이 바친다는 것이었다. 최근에는 그 노력이 결실을 맺어, 이른바 TR1이 발표되면서 14개의 새로운 라이브러리가 C++에 도입되었다(항목 54 참조). 이들 중 무려 3분의 2 이상이 이미 부스트에서 배포하고 있는 것들이었다.

두 번째는 라이브러리 승인 과정이다. 이곳의 승인 과정은 공개 동료 심사(public peer review)에 기반을 두고 있다. 우리가 만든 라이브러리르 부스트에 한 번 내 보고 싶다면, 일단 부스트 개발자 메일링 리스트에 우리가 만든 라이브러리가 얼마나 관심을 끄는지 알아본다. 어느 정도 됐다 싶으면 우리의 것에 대해 사전 검사가 시작되는데, 부스트 사이트에서 말하는 소위 “토론-수정-다시 제출, 이 과정이 만족할 때까지 반복”의 고리를 열심히 돌게 된다.

이 과정이 끝나면 라이브러리를 공식적으로 제출하기 위한 형식을 갖추게 되는데, 이때 심사 관리자가 우리의 라이브러리가 부스트의 최소 요구사항을 만족하는지를 확인해 준다. 예를 들어, 부스트 라이브러리가 되려면 일단 컴파일이 되는 컴파일러가 최소한 두 개는 되어야 하고, 일정한 라이선스에 따라 배포할 수 있는지의 여부가 입증되어야 한다. 이 과정이 끝나면 비로소 우리의 것들이 부스트의 쟁쟁한 개발자 모임에 공식 심사 대상으로 제출된다. 심사는 지원자들이 맡게 되며, 이들은 주어진 심사 기간 동안 우리의 제출물을 검토하면서 다음과 같은 주안점을 두어 평가를 진행한다.

  • 설계와 구현이 얼마나 우수하게 되어 있는가?
  • 다른 컴파일러와 운영체제들에 맞추어 이식할 수 있는 코드인가?
  • 대상 사용자들, 다시 말해 이 라이브러리가 도움을 줄 수 있는 특정 분야 종사자에게 쓸모 있는 라이브러리인가?
  • 문서화가 명료하고, 완전하고, 정확하게 되어 있는가?

각각에 대한 의견을 담은 심사 결과는 부스트 메일링 리스트를 통해 공개적으로 올라온다. 그렇기 때문에 심사를 진행하는 당사자와 그 외의 사람들이 그 심사 결과를 보고 다른 의견을 나눌 수도 있게 된다. 이렇게 저렇게 심사 기간이 끝날 무렵이 되면, 심사 관리자는 그간의 의견을 취합하여 우리의 라이브러리에 대해 승인, 조건부 승인, 혹은 기각을 결정한다.

이러한 동료 심사 시스템 덕택에 함량 미달의 라이브러리가 부스트에 발을 들여 놓는 일이 효과적으로 차단되었을 뿐만 아니라, 라이브러리를 한 번 제대로 만들어 보고 싶은 사람들에게 업계 수준의 교차 플랫폼 라이브러리를 설계/구혆하고 문서화하는 방법이 자연스럽게 전수되는 효과를 낳았다. 부스트의 이름하에 배포되는 라이브러리로서 승인이 되려면 최소한 두 번 이상의 공개 심사는 기본이라는 것이 이 동네의 상식이다.

현재 부스트에서 배포되는 라이브러리는 수십 개이고, 지금도 새로운 것들이 속속 추가되고 있다. 기존의 라이브러리가 없어지는 경우도 더러 있는데, 대개 기존의 기능을 지원하되 기능이 더 좋고 설계도 더 깔끔하게 빠진 라이브러리가 나와서 이것으로 대체되는 경우이다.

부스트 라이브러리는 크기도 범위도 아주 제각각이다. 극단적으로 작은 녀석은 개념적으로 코드 몇 줄만 쓰면 되는 것도 있다(물론 에러 처리와 이식을 위한 부분이 덧붙은 후에는 대게 더 길어진다). 이런 라이브러리의 예가 컨버전(Conversion)이다. 이 라이브러리는 C++의 것보다 더 안전하고 편리한 캐스트 연산자를 제공한다. 예를 들면 numeric_cast 함수는 어떤 값을 다른 타입으로 변환하다가 오버플로나 언더플로 혹은 이들과 비슷한 문제가 생기면 예외가 발생하도록 되어 있고, lexical_cast 함수는 operator«를 지원하는 타입이면 어떤 것도 string으로 캐스팅해 주는 함수이다. 진단이나 로그 기록 등에 쓰면 무척 좋을 것이다. 이런 깜찍한 라이브러리가 있는 반면, 무지막지한 라이브러리도 있다. 제공하는 기능 자체가 엄청나다 보니 그것만을 다룬 책이 나올 정도인데, 대표적인 예로 부스트 그래프 라이브러리(Boost Graph Library)부스트 MPL 라이브러리(메타프로그래밍 라이브러리)가 있다.

이렇게 분야와 깊이를 가리지 않고 정말 많은 부분에서 가려운 곳을 시원하게 긁어 주고 있는 부스트의 라이브러리 군단은 크게 십수 개의 범주로 나뉘어 있다. 여기에 각각을 소개한다.


  • 문자열 및 텍스트 처리

주요 구성요소로 타입 안정성을 갖춘 printf 비슷한 서식화 기능, 정규 표현식 및 토큰화 및 구문 분석 기능이 있다.


  • 컨테이너

STL 양식의 인터페이스를 제공하는 고정 크기 배열, 가변 크기 비트세트, 다차원 배열 등이 포함되어 있다.


  • 함수 객체 및 고차(high-order) 프로그래밍

TR1의 기능을 구현하는 데 사용된 몇 개의 기반 라이브러리가 여기에 해당된다. 이들 중 재미있는 것을 하나 소개하면 람다 라이브러리를 들 수 있다. 이 라이브러리는 별도의 준비 없이 즉석에서 함수 객체를 생성해주는 기막힌 기능을 제공하는데, 우리도 자신이 무엇을 했는지를 자각하지 못할 정도로 감쪽같다. 아래의 예제를 보자.

1
2
3
4
5
6
7
using namespace boost::lambda;
...
std::vector<int> v;
...
// v 안에 들어 있는 각각의 원소 x에 대해 x*2+10을 출력한다.
// "_1"은 람다 라이브러리에서 사용하는 '현재 원소'를 뜻하는 자리채움자이다.
std::for_each(v.begin(), v.end(), std::cout << _1 * 2 + 10 << '\n');


  • 일반화 프로그래밍

텍사스 소떼처럼 득실득실한 특성정보(traits) 클래스를 만날 수 있다. 특성정보에 대한 자세한 이야기는 항목 47에서 확인하자.


  • 템플릿 메타프로그래밍(TMP, 항목 48 참조)

컴파일 타임 단정문, 부스트 MPL 라이브러리 등이 여기에 포함된다. MPL에는 아주 감동스런 물건들이 맣지만 그 중 하나를 꼽는다면 타입 등의 컴파일 타임 객체를 STL스러운 자료구조로 관리할 수 있도록 지원한다는 것이다. 역시 예제를 보자.

1
2
3
4
5
// 세 개의 타입(float, double, long double)을 담는 컴파일 타임 컨테이너를 list 비슷하게 만들고, 이것을 "floats"라고 부른다.
typedef boost::mpl::list<float, double, long double> floats;

// 기존의 "floats"에 들어 있는 타입 집합은 물론이고 그 집합의 앞에 "int"가 삽입된 새로운 타입 리스트를 만들고, 이것을 "types"라고 부른다.
typedf boost::mpl::push_front<floats, int>::type types;

타입을 담아 관리할 수 있는 이런 컨테이너[타입리스트(typelist)라고 알려져 있다. 실제로는 mpl::list로도 쓸 수 있고 mpl::vector로도 쓸 수 있다] 덕택에, 정말 강력하고 많은 주목을 받고 있는 TMP의 무궁무진한 활용이 더욱 쉬워졌다고 할 수 있다.


  • 수학 및 수치 조작

유리수, 4원수(quaternion) 및 8원수(octonion), 최대 공약수 및 최소 공배수, 난수 등이 포함된다.


  • 정확성 유지 및 테스트

암시적 템플릿 인터페이스(항목 41 참조)를 형식화해 주는 라이브러리와 테스트 우선 프로그래밍을 가능하게 해 주는 라이브러리가 있다.


  • 자료구조

타입 안정성을 갖춘 공용체(크기와 쓰임이 가지가지인 “어떤” 타입도 담을 수 있는 자료구조), 그리고 TR1에서 지원하는 것의 뿌리격인 바로 그 튜플 라이브러리(항목 54 참조)가 이 범주에 들어간다.


  • 타 언어와의 연동 지원

C++와 파이썬 사이의 걸림돌 없는 상호운용을 가능하게 하는 라이브러리도 지원한다.


  • 메모리

고성능의 고정 크기 할당자를 지원하는 풀(Pool) 라이브러리(항목 50 참조) TR1에도 일부 포함된 가지각색의 스마트 포인터가 이 범주에 들어간다. TR1에 속하지 않은 스마트 포인터 중 하나가 scoped_array이다. 동적으로 할당된 배열에 대해 동작하는 auto_ptr 같은 스마트 포인터라고 보면 된다.


  • 기타

CRC 점검, 날짜 및 시간 조작, 파일 시스템 횡단 등을 지원하는 라이브러리가 주요 구성요소이다.


필자가 위에서 말한 몇 개의 라이브러리는 부스트 사이트에서 자주 눈에 띄게 될 것들을 뽑아 놓았을 뿐이라는 점도 덧붙여 말한다. 전부 소개하자면 너무 길다.

이렇듯 부스트에서 배포하는 라이브러리는 정말 많은 일을 도와주고 있다. 하지만 드넓고 거친 프로그래밍의 모든 세계를 망라하고 있는 것은 아니다. 이를테면 부스트 라이브러리에는 아직 GUI 개발을 지원하는 라이브러리도 없고, 데이터베이스 연동 지원도 없다. 최소한 지금, 그러니까 이 글을 쓰고 있는 시점에선 그렇다. 하지만 우리가 이 글을 읽고 있을 때면 이런 라이브러리가 지원될 수 있으니 부스트 사이트를 살펴보자.

부스트는 동료 심사를 거쳐 등록되고 무료로 배포되는 오픈 소스 C++ 라이브러리를 개발하는 모임이자 웹사이트이다. 또한 C++ 표준화에 있어서 영향력 있는 역할을 맡고 있다.

부스트에서 배포되는 라이브러리들 중엔 TR1 구성요소에 들어간 것도 있지만, 그 외에 다른 라이브러리들도 아주 많다.


참고

This post is licensed under CC BY 4.0 by the author.