C++의 템플릿은 두말할 나위도 없이 매우 강력하고 유용한 개념이다.
C++에다가 제네릭/메타 프로그래밍--프로그램을 만드는 프로그램.. 즉 더욱 추상화된 기법--의 가능성을 무한히 열어 줬기 때문이다.

swap, min, max 같은 것부터 시작해서
옛날에는 문법적으로 매우 불완전하기 짝이 없는 매크로를 쓰고 위험한 typecasting을 해야 했던 것을, 템플릿 덕분에 언어의 정식 문법으로 아주 깔끔하고 type-safe하게 구현할 수 있게 된 게 많다.

그리고 static 배열의 크기(원소 개수)를 되돌리는 매크로인 ARRAYSIZE도 생각해 보자.
C 시절에는 sizeof(x)/sizeof(x[0]) 와 같은 식으로 구현했다. 물론 이 값들은 모두 컴파일 시간 때 이미 결정이 되기 때문에 실제로 나눗셈 연산이 일어나지는 않는다.

하지만 템플릿을 이용하면...
template<typename T, size_t N> char (*__GetArraySize(T (&n)[N]))[N];
#define ARRAYSIZE(x)   sizeof(*__GetArraySize(x))

자 이게 무슨 의미인지 이해가 가시는가?
무슨 타입인지는 모르겠지만 어쨌든 N개짜리 배열의 참조자를 받아서 N개짜리 char형 배열의 포인터를 되돌리는 함수를 템플릿으로 선언한다.
그 후, N의 함수의 리턴값을 역참조한 배열의 크기를 sizeof로 구하면... 당연히 char형 배열의 크기는 본디 배열의 원소 개수와 일치할 수밖에 없게 된다. 함수의 몸체를 정의할 필요조차 없다! 마치 &( ((DATA*)NULL)->member ) 가 에러를 일으키지 않고 멤버 오프셋을 되돌려 주는 것과 같은 이치이다.

템플릿이 아예 배열의 참조자를 받게 함으로써 좋은 점은, 이 매크로에다가 static 배열이 아니라 단순 포인터를 집어넣으면 컴파일 에러가 발생하게 된다는 것이다. 단순 sizeof(A)/sizeof(A[0])보다 타입 safety도 보장되고 훨씬 더 좋다.
(), *, [] 등이 뒤섞인 복잡 난해한 C/C++ type string 읽는 법에 대해서는 이전에 별도로 글로 다룬 바 있다.

template<typename T, size_t N> size_t ARRAYSIZE(T (&n)[N]) { return N; }

물론 위와 같이 코드를 쓰면 더 간단하고 알아보기도 쉽게 동일한 효과를 이룰 수 있지만, 최적화를 하지 않은 디버그 빌드는 불필요한 함수 호출이 계속 일어나는 문제가 있다. 배열의 크기 정도는 컴파일 타임 때 모든 계산이 딱 일어나게 하는 방법을 쓰는 게 더 좋을 것이다.

이렇게 템플릿은 매우 편리한 개념이긴 하나, 한계도 분명 있다.
템플릿은 동일한 패턴을 지닌 여러 다른 코드들을 찍어내는 ‘틀’과 같다. 하지만 이 틀 자체는 한 번만 정의해 놓고 링크 타임 때 여러 오브젝트 파일 사이를 자유자재로 넘나들게 할 수는 없다. 최적화처럼 기술적으로 여러 난관이 있기 때문이다. 템플릿 인자로 들어온 타입이 int일 때, double일 때, 심지어 개당 100바이트가 넘는 구조체일 때 이들에 대한 각종 비교나 대입 연산과 최적화 방식과 코드 생성 방식은 완전히 천차만별이 될 수밖에 없다.

이런 이유로 인해 템플릿의 정의 효과는 오로지 한 소스 코드, 한 translation unit 안에서만 유효하며, 템플릿 클래스나 함수는 모든 소스 코드에 헤더 파일의 형태로 매번 include되어야 한다. 몸체까지(body; definition; implementation) 죄다 말이다. 일반적인 클래스의 함수처럼 선언 따로, 정의 따로일 수가 없다. 사실은 템플릿 코드에 대한 에러 체킹 자체도 템플릿이 인자가 들어와서 어떤 형태로든 실현(realize)이 됐을 때에야 할 수 있다.

그러니 템플릿으로 구축된 각종 함수와 클래스는 소스 코드가 노출될 수밖에 없으며, 그 소스 코드를 고치면 템플릿을 include하는 모든 소스 파일들이 재컴파일되어야 하는 등 프로그래밍 상의 한계가 결코 만만한 수준이 아니다. 하지만 이 한계는 C++ 언어의 컴파일/링크 모델이라든가 기존 컴파일러들의 오브젝트 파일 포맷 내지 컴파일러/링커의 동작 방식이 근본적으로 바뀌지 않는 한 극복되기 쉽지 않을 것이다.

이런 구조적인 불편을 해소하고자 C++ 표준 위원회가 제안한 것은 export 키워드이다.
흔히 import/export하면 윈도우 프로그래밍 세상에서는 DLL 심볼을 내놓거나 가져오는 개념을 떠올리는데, 이 문맥에서는 그런 건 아니다. 한 translation unit에 존재하는 템플릿 함수 구현체를 다른 translation unit이 그 경계를 초월하여 링크 타임 때 가져다 쓸 수 있게 하는 흠좀무한 개념이다. 즉, 템플릿 몸체에 대한 export를 뜻한다.

헤더 파일에다가는

export template<typename T> void Swap(T& a, T& b);

이라고 해 놓고 모처의 cpp 파일 한 군데에다가는

export template<typename T> void Swap(T& a, T& b)
{
    T c(a); a=b, b=c;
}

이런 식으로 써 놓음으로써,
Swap 함수는 export라고 마크가 되어 있으니 자기 translation unit에서만 쓰지 말고, 다른 단위에서도 필요하면 링크 때 가져다 쓸 수 있게 한다는 게 당초 의도였던 모양이다. (인터넷 검색을 해 보니)
템플릿으로 들어간 클래스 멤버 함수에 대해서도 마찬가지이다. export가 없으면 저 함수 body는 마치 static 변수/함수처럼 그 소스 파일 내부에서만 유효하고 다른 소스 파일에서는 링크 에러가 나게 될 것이다.

그러나 이것을 본 컴파일러 개발사들은 '이뭐병, 이딴 걸 무슨 얼어죽을 표준안이라고 내놓냐' 하는 반응이었고..
비주얼 C++, gcc 등 유수의 컴파일러들은 이 키워드의 구현을 포기/거부하고 말았다.
비주얼 C++의 경우 도움말에 Nonstandard Behavior로 자기는 이 키워드를 지원하지 않는다고 당당히 명시까지 되어 있다. 2010은 모르겠고 2008까지도 마찬가지임.
하지만 마이너급 컴파일러 중엔 export를 구현 안 한 놈이 없는 건 아니라고 한다. 흠좀무.

컴파일해 놓은 코드를 짜깁기만 하는 게 링크인데, 저걸 구현하려면 링크에다가 다시 컴파일을 하고 재링크(?) 과정을 집어넣어야 한다. C/C++의 정상적인 빌드 루트와는 정면으로 모순되는 과정을 요구하는 것이다. 그래서 컴파일러 개발사들이 떡실신한 것이다.

어쨌든 이런 이유로 인해서 export는 C++의 흑역사 키워드로 전락해 있다.
옛날에 MS는 링크 과정을 최대한 간단하게 만들려고 COFF 방식 obj 파일과 PE 방식 exe 파일을 채택했다고 하던데, 하지만 요즘은 워낙 translation unit을 넘나드는 링크 타임의 코드 생성과 전역 최적화 같은 기술이 대세가 돼 있다 보니, export 키워드의 의도가 옛날만치 그저 병맛나게 들리지만은 않을 것 같은 생각이 들기도 한다.

마치 유명무실하던 auto가 리모델링되고 서울 지하철 5호선 마곡 역이 13년만에 부활했듯이 export 키워드도 의도 자체는 좋은데.. 언젠가 부활할 날이 올 수 있지 않을까? 하지만 C++의 차세대 표준인 C++0x에서는 export를 아예 빼 버리고 백지화하자는 제안까지 나온 상태이니, 과연 지못미이다.

Posted by 사무엘

2010/06/12 09:27 2010/06/12 09:27
, ,
Response
A trackback , 11 Comments
RSS :
http://moogi.new21.org/tc/rss/response/293

Trackback URL : http://moogi.new21.org/tc/trackback/293

Trackbacks List

  1. ☆~의 생각

    Tracked from iruis' me2day 2011/01/18 15:25 Delete

    트위터 타임라인에서 보게 된 템플릿의 export 키워드. 닷넷같은 바이트코드가 아니면 구현이 안될 줄 알았는데 C++도 가능 하긴 하구나. 근데 이게 구현되면 컴파일러끼리 호환이 문제 될듯, 그

Comments List

  1. 주의사신 2010/06/12 09:46 # M/D Reply Permalink

    Visual Studio 쓰다가 export를 써 봤는데, 파란 색으로 표시해 주길래, 우와 드디어 VS가 export를 지원하는구나 생각을 하고 썼습니다. 컴파일 결과,

    "미래의 사용을 위해 예약 중"

    이라는 문구가 뜨더군요.(영문판을 쓰기 때문에 정확한 문구가 기억이 나지는 않습니다. 써 보면 되긴 합니다만...)

    그래서 다시 template을 원래대로 돌려놓았던 기억이 납니다.

  2. 김 기윤 2010/06/12 13:50 # M/D Reply Permalink

    Visual Studio 2010 에서 확인

    warning C4237: 'export' keyword is not yet supported, but reserved for future use

    주의사신님이 말씀하신 것과 같은 에러가 뜹니다 (..)

    문법적으로는 있으면 좋은데, 실제 구현이 정말 어려워서 죽어버린 키워드? (..)

  3. 사무엘 2010/06/12 16:18 # M/D Reply Permalink

    비주얼 C++ 2003은 export를 예약어로 전혀 인식하지 않으며(export 뭐임? 먹는 거임? 우걱우걱.. 컴파일러가 먹는 건 일반 심볼들.. ㅋㅋ),
    참고로 덧붙이자면 vector<list<int>> ap; 도 2003에서는 에러가 나는 반면, 2008은 정상 처리되네요. (닫는 꺽쇠를 > >로 띄워야 함)
    2005는 결과가 어떤지 모르겠습니다.

    말이 나왔으니 말인데, C++0x는 정말 기괴한 변화가 많아서 컴파일러 구현의 난해함은 한층 더 안드로메다로 갈 듯합니다.
    R-value 참조자 && 는 가히 억소리 나고
    특히 람다 대수 개념 도입 덕분에(함수도 자유롭게 value 취급) 이제 C++도 사실상 nested function 구현이 가능해진 거나 마찬가지로 보입니다.

    1. 김 기윤 2010/06/12 16:48 # M/D Permalink

      가히 '억' 소리 나는 C++0x 의 변화 ㄲㄲㄲ

      람다 함수는 정말 ...... 자유도는 상승했지만, 난이도도 덩달아..

  4. 김재주 2010/06/12 19:53 # M/D Reply Permalink

    이쯤되면 아예 C++의 호환을 포기해버리는 것도 한 방법이 아닐까 싶군요.

    사실 C도 C++도 너무 낡았죠?
    C#만 믿고 갑시다. M$님이 다 해주실 거야..

    1. 김 기윤 2010/06/12 21:35 # M/D Permalink

      그런데 기존에 C/C++ 으로 쌓아올린 것의 양이 너무 많다는 것이 문제입니다. C++ 의 호환을 포기하고 C# 만 믿고 가려면 기존의 라이브러리 등을 모두 포팅해야 하는데 과연...

      + 그리고 게임 등의 속도가 중요한 프로그래밍은 수행속도 등의 문제 때문에 C# 으로는 좀 곤란...합니다. 좀 더 하드웨어 성능이 올라가면 모르겠는데, 현재로써는...

  5. 김재주 2010/06/13 00:03 # M/D Reply Permalink

    김 기윤님께 //
    지금까지 쌓아둔 코드의 양이 많아서 다른 언어로 넘어갈 수 없다면, 50년이 지나도 100년이 지나도 계속 C,C++을 사용해야겠죠. 그건 아무리 생각해도 현명한 일은 아닙니다. 현대 컴퓨터 언어들이 그동안 이뤄낸 많은 발전들을 모두 포기해야 하니까요.

    그리고 게임 엔진 정도라면 C++로 구현하나 C#으로 구현하나 속도면에서 대단한 차이가 나지는 않습니다. 왜냐면 요즘 게임들은 많은 경우 CPU보다는 GPU의 연산량이 더 많은 경우가 많고, 컴퓨터들도 2개 이상의 CPU 코어를 가지고 병렬로 처리하기 때문이죠.

    오히려 Memory leak 등이 잘 발생하지 않기 때문에 C#이나 JAVA를 이용하는 편이 나은 부분도 있습니다. 이로 인한 오버헤드가 있긴 하지만 개발 편의 같은 부분에서 얻는 트레이드 오프 관점으로 생각하면 됩니다.


    다만 지금까지 만들어진 게임 엔진들이 C++을 기반으로 만들어져 있고, XBOX 360이나 PS3용으로 .NET 프레임워크가 포팅되지 않았기 때문에 사용하지 않고 있을 뿐입니다.

  6. 사무엘 2010/06/14 13:14 # M/D Reply Permalink

    김재주 님 C# 꽤 좋아하시는 듯.. ㅋ
    차라리 C++ 복잡 흉악함에 대한 대안은, 동급의 네이티브 코드 생성 언어인 D 언어가 될 수도 있겠다는 생각이 드네요.

  7. 김재주 2010/06/15 14:40 # M/D Reply Permalink

    C#처럼 실무에도 써 먹을만 하면서 파 보면 재밌는 언어도 흔치는 않죠.

    만약 네이티브 코드 생성이 반드시 필요하다면 D보다도.. 아예 새로 디자인하는 게 낫지 않을까 생각합니다.

  8. 김기윤 2011/01/18 15:26 # M/D Reply Permalink

    C#이라던가 Java 를 수업시간 등을 통해서 좀 익힌 뒤로 저의 관점이 좀 바뀌었습니다.

    많이 알아야 많이 보이는 듯 합니다...


    다만 왠지 C#이나 Java 는, (게임 프로그래머의 입장인 저한테는) 왠지 표현력이 적다...랄까, 답답하다는 느낌이 들었습니다. 단순히 익숙하지 않아서 그럴 수도 있겠지만, 왠지 의존적인 성향이 강하달까.. 그런 느낌이 듭니다. 아이러니한건, 의존성으로 따지면 객관적으로 볼때 C++ 이 최악이라는 건데도 이렇게 느낀다는 것...

    1. 사무엘 2011/01/18 21:14 # M/D Permalink

      무슨 뜻인지 알겠고 C/C++ 중독자-_-로서 저 역시 그 관점에 동의해요.
      C#과 자바의 생산성은 C/C++이 가히 엄두도 못 낼 수준인 건 사실입니다.
      허나 C/C++만이 지니고 있는 그 자유도와 포스는.. 소위 C/C++의 단점을 보완했다는 후대 언어들에서는 결코 찾을 수 없을 것입니다.
      참고로 저도 자바와 C# 중에서는 C#이 더 좋습니다. 그냥 괜히 느낌이요. ^^;;

Leave a comment
« Previous : 1 : ... 1190 : 1191 : 1192 : 1193 : 1194 : 1195 : 1196 : 1197 : 1198 : ... 1450 : Next »

블로그 이미지

철도를 명절 때에나 떠오르는 4대 교통수단 중 하나로만 아는 것은, 예수님을 사대성인· 성인군자 중 하나로만 아는 것과 같다.

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2018/11   »
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30  

Site Stats

Total hits:
1055090
Today:
82
Yesterday:
580