본문 바로가기

Programmer.cpp/Cplusplus.cpp

C++ 버전별 변천사 #2 - C++03

지난번 글을 경어로 적었더니 살짝 멀미가 나더군요. 그래서, 이번 글 부터는 죄송스럽지만 경어를 사용하지 않고 작성하도록 하겠습니다. (버릇없어 보인다거나, 글이 길어져도 좋으니 경어로 된 글을 보고 싶으시다는 분이 계신다면 안타깝지만 직접 경어로 인터프리팅 하시거나...)




이번에 살펴볼 버전은 2003에 표준으로 등록된 C++03 이다.

나올 당시에는 C++의 완성판이다 라는 평가도 있었지만, 다음 버전이 나올 때 보니 그건 아니었던 걸로.

사실, 언어적인 입장에서 변한 점은 별로 없다. 다만, C++03의 가장 큰 특징은 STL이 정식으로 C++의 표준 라이브러리로 제공되었다는 점이 중요하다. 덕분에 많은 골수 C++ 개발자들에게는 환영받았지만, 이 때 부터 정말로 '배우기 어려운 언어'가 되어버렸다. 그도 그럴 것이 C++98에서는 template를 그냥 C++의 고급기능 정도로 치부하고, 배우지 않아도 대충 쓰는데는 지장없는 것으로 취급되었었지만, 이제는 그랬던 사람들도 template을 제대로 배워야 하는 시점이 와버렸기 때문이다.


언어적 측면에서의 C++03

별로 추가된 사항이 없다. bool type이 새로 추가되었다는 점과 C++스타일의 형변환 연산자, RTTI (Run-time type information)가 추가되었다는점. 그리고, namespace가 추가되었다. 진짜다. 이게 전부다.

지난번 글에서 밝혔듯이 C++98만 익힌 분들을 대상으로 글을 적는 것이니 만큼, 하나하나 간단히 살펴보고 넘어가자.


bool type은 이전까지 int 로 대신했던 (0이면 거짓, 0이 아니면 참으로 인식했었다)것을 정식 자료형으로 만든 것 뿐이다. 감사하게도 메모리도 1바이트 밖에 차지하지 않는다. (1비트면 충분하겠지만 말이다) value로는 true와 false 라는 키워드로 사용할 수 있다. 물론, 표준 자료형답게 자동으로 형변환도 일어나고, 실제로 내부값이 0이면 false로 인식한다. bool b = 0; 과 같이 사용해도 아무런 문제가 없다는 소리다. 그렇다고, 이렇게 쓰면 bool 타입을 정성껏 만들어준 C++03에 대한 예의가 아니니 bool b = false; 와 같이 쓰라는 대로 써주자.


C++스타일의 type casting은 네 가지다.

static_cast, dynamic_cast, reinterpret_cast, const_cast가 그것이다.

이거 그냥 C에서 처럼 i = (int) f; 이런 식으로만 쓰면 편할건데 왜 만들었나 싶을지도 모르겠다.

그런데, 문제가 있다. C++에서는 클래스간의 상속관계가 있고, 거기에서 virtual 멤버를 가지고 있을 경우 내부적으로 virtual table을 관리하는데, C 스타일로 이 상속관계에 있는 클래스들을 함부로 형변환 했다가는 이 virtual table을 싹 날려먹어서 run-time 에러를 유발할 가능성이 생기기 때문이다. (실제로 C++98에서는 이런 문제를 해결할 방법이 없었다) 그래서 만들어 진 것이 dynamic_cast이다. dynamic_cast를 사용할 경우, virtual table을 포함한 object를 안전하게 형변환 할 수 있다. 그러다보니, 원래 C 스타일의 형변환과 비슷한 방식도 가능해야겠다 싶으니 static_cast와 reinterpret_cast도 만들어졌고, 변수의 상수성을 날려버리는 const_cast도 만들어졌다.

(실제로 C 스타일의 형변환을 실행하면 static_cast, reinterpret_cast, const_cast 가 동시에 행해지는 역할을 한다. 당연하게도 하위 호환성을 유지하는 C++에서는 C 스타일의 형변환도 사용할 수 있다)

  • static_cast는 흔히 생각할 수 있는 형변환이다. 컴파일러 수준에서 형변환에 적합하지 않은 타입으로의 변환을 시도할 경우 에러나 경고를 발생시키기도 한다. (int i; char* pc = static_cast<char*>( i ); // 이건 에러다)
  • dynamic_cast는 virtual table의 상관관계를 포함한 형변환이다. 이 부분은 나중에 더 자세히 살펴볼 기회를 가져볼까 한다.
  • reinterpret_cast는 강제적인 형변환이다. static_cast와는 달리 적합하지 않은 타입으로의 변환도 가능하다. 예를 들어, int를 static_cast를 이용해 void* 로 변환하려 할 경우 에러를 발생시키지만, reinterpret_cast로는 가능하다. 간혹, 특정 class를 바이트단위로 쪼개서 사용해야 할 경우가 생긴다거나 할 때 유용하게 쓰이기도 한다.
  • const_cast는 const 변수의 상수성을 제거하여 값을 변경시킬 수 있는 변수로 변환하는 형변환이다. 사실, 이 녀석은 좀 위험한데, side-effect를 없애기 위해 기껏 const 함수를 만들었다가도 이 변환 한 번으로 side-effect를 유발시킬 수 있게 된다. 사용에 많은 주의가 필요한 형변환이다. (요상한 내용의 C 프로그램을 포팅하는 경우가 아니라면 거의 쓸 일 없다고 보는게 좋다)


RTTI는 사실 중요한 부분이다. C나 C++를 사용할 경우, 특히 void* 타입으로 매개변수를 전달받았을 때 실제로 이놈이 뭘 가리키고 있는 포인터인지 알면 좋겠다는 경우를 많이 겪어봤을거다. C++에서는 이런 경우가 더 빈번히 발생하는데, 복잡한 구조의 클래스 계층 때문에 발생하는 경우가 많다. 그래서, typeid 이라는 연산자를 제공해서 이러한 문제를 해결할 수 있도록 해주고 있다. 그런데, 이거 사용하는걸 별로 권장하질 않는다. 이유는 딱 하나, "느리다"는 이유다.

사용방법은 그리 어렵지 않다. #include <typeinfo> 해주고 쓰면된다. typeid(int) 라고 적던가, typeid(i) 라고 적으면 해당 타입이나 변수의 type_info를 얻어낼 수 있다. 하지만, run-time 이라는 이름이 들어간 순간 "느리다".

대부분 "느리다"의 문제는 "꼭 필요할 때만 써라"는 결론을 얻게 되고, 꼭 필요한 순간을 최소화 하기 위해서는 "잘 설계해라"는 결론이 얻어진다. 왠만하면 잘 설계해서 RTTI는 쓸 일 없게 만드는게 정신건강에 이롭다.


namespace는 좀 세련되게 표현하자면 user-defined scope by name 정도로 표현할 수 있겠다. 잘만 활용하면 써먹을 데가 많다. 실제로 해외의 프로그래머들은 이걸 가지고 incremental build에 활용하기도 하고, 버전관리에도 많이 사용하는 것을 볼 수 있다. (아쉽게도 국내에선 거의 본 기억이 없다! 당장 이 글을 쓰는 필자도 잘 안쓴다. 사용법도 어렵지 않으니 못쓰는건 아니다. 단지, 귀찮다 -_-;;)

사용법은 간단하다. namespace Name { ... }; 이렇게 적어놓고 안에 들어가는 모든 내용들이 Name이라는 이름으로 묶인다. 만약 namespace Name { int i; }; 라고 정의했다면 Name::i 라는 이름으로 사용할 수 있다. 일일이 Name:: 이라는 prefix를 붙이기 귀찮다면 using Name::i; 라고 해주거나 using namespace Name; 이라고 해주면 그 이 후부터는 i 라는 이름으로 사용할 수 있다.

사용법이 쉽다고 했지, 그 속사정이 쉽지는 않다. (필자가 귀찮아서 안쓰는 이유에도 한몫하는 부분이다)

using Name::i; 를 선언하면, 이 때부터 i 로 사용할 수 있다고 했다. 그런데, 로컬에서 다시 int i; 를 선언해도 오류가 아니다. 그럼 이 때 i = 10; 이라고 적으면 i 인지 Name::i 인지는 전적으로 프로그래머가 판단해서 사용해야 한다는 결론이 생긴다. namespace와 using은 identifier의 소속을 찾아가는 순서를 지정하고, scope를 명확히 하는 역할만을 하기 때문에 이 경우엔 로컬에서 선언된 i가 우선되고, Name::i 와 같이 명시적으로 사용할 경우에만 Name::i를 사용할 수 있다. 이 쯤 되면, namespace를 어떤 경우에 써먹어야 할지 감이 오는 분들도 계시겠지...

사실, namespace는 C++03에서 중요하게 다루는 부분 중 하나이기 때문에 그냥 넘어갈 부분은 아니다. 그래서, 다음 기회에 좀 더 자세히 살펴보기로 하고 여기서는 이 정도로 선을 긋도록 하겠다.


C++03 의 문법적인 부분은 이 정도가 전부다. 이제 남은건 STL이라는 어마어마한 녀석이 남았다.


STL은 원래 C++의 표준이 아니었다. 흔히 Generic Programming이라는 표현을 쓰는 '데이터 타입에 구애받지 않은 프로그램 구현'을 목표로 한 시도가 이전에도 많이 있었다. 그러던 중 C++의 template가 이에 적합하다는 의견이 많았고, AT&T와 HP에서 C++로 generic programming을 구현하려던 노력의 결과물이 바로 STL이다. (지금은 실리콘 그래픽스에서 주관하고 있는것 같던데, 정확히는 모르겠다)

현 시점에서, STL은 C++의 표준 라이브러리로 포함되었고, 덕분에 C++만 배우면 쉽게 스택이나 큐 같은 자료구조도 사용할 수 있게 되었다. 뭐, 언제나 그렇듯이.. 배우고 나면 쉽겠지만 배우기 어려워졌다는건 부정할 수 없다.

STL은 크게 container와 algorithm 으로 구분되고, 그 외에 잡다한 utility와 string, stream 등 프로그래밍에 도움을 줄 수 있는 것들로 구성된다. (심지어, C와의 호환을 위해 C 스타일의 CRTL도 포함하고 있다)

STL만 따로 떼어놓아도 두꺼운 책이 한 두 권 나올 정도로 그 분량이 어마어마하다. 그렇다고 정말 그 분량을 다 배워야 써먹을 수 있느냐 하면 그건 아니다. 기본만 알면 나머지는 그 때 그 때 구글링만 해도 충분히 써먹을 수 있다. STL 관련된 부분은 C++ 변천사 시리즈를 다 쓴 다음 차근차근 하나씩 살펴보도록 하겠다. (메뉴얼을 기대하진 말자)



여기까지가 C++03의 요약입니다.

필자도 책이나 문서를 달달 외우고 다니지는 않는 관계로 혹시나 빠진 부분이나 보충해야 할 부분들이 있을지 모르니, 그런 부분이 보일 땐 언제든 댓글을 남겨주시기 바랍니다. 하지만, 아직까지는 그냥 준비 운동이라는 점. 최근엔 워낙 SNS같은 단문만 적는데 익숙해져서 글 쓰는데 익숙해지기가 좀처럼 쉽지 않네요.


수정 2015.01.24 : 제가 한 가지 잘못 알고 있었더군요. C++0x는 C++11의 옛 이름이었고, 2003년에 ISO에 표준화된 버전은 C++03으로 부르는 것이 맞습니다. 전부 수정했습니다.

'Programmer.cpp > Cplusplus.cpp' 카테고리의 다른 글

C++ 버전별 변천사 #3 - C++11  (0) 2015.02.01
C++ 버전별 변천사 #1 - C++98  (0) 2015.01.20
C++ 이야기를 시작하면서  (0) 2015.01.18