C++의 template부럽지 않은 void type

이한길의 이미지

아래 "C++은 C의 확장?"라는 괜찮은 토론이 있었습니다. 하지만 조금 주제가 넓어서 그런지 흐름이 이리저리 움직이는 것 같네요. 조금 구체적인 이야기를 하고 싶어지는군요.

jj wrote:
개인적으로 C에 템플릿만 추가된다면, 시스템 프로그래머는 더이상 바랄것이 없을것 같습니다.

위 토론중에 jj님이 이런 이야기를 하셨습니다. 물론 template라는 C++의 훌륭한 기능이 C에 추가되는 것도 괜찮겠지만 가만히 생각해보면 C에서는 void라는 type을 사용하는 것이 더 적합하지 않나 싶습니다. 물론 같은 기능을 하는것은 아니지만 그럭저럭 잘 사용할 수 있지요. 간단한 예를 보면..

int x=10;
void *t=&x;
int *y=(int *)t;

printf("%d\n", x);
printf("%d\n", *(int *)t);
printf("%d\n", *y);

알 수 있듯이 모두 10이라는 결과를 출력합니다.

void라는 type은 위의 예처럼 int를 담을 수도 있지만 다른 type도 담을 수 있습니다. 보통 template를 사용하는 경우에는 기본적인 동작은 동일하지만 그 외의 다른 필드들이 사용되는 경우 유용하지요. 예를 든다면 링크드 리스트같은 것이 있겠지요. 하지만 링크드 리스트의 구조를 다음과 같이 정의하면 어떨까요?

struct _linkedlist_
{
  void *data;
  struct _linkedlist_ *next;
};

그리고 data에는 필요한 다른 구조체를 정의해서 집어 넣어 사용하는 것입니다. 괜찮은 생각이 아닐까 싶은데요... 다른 경험들 있으시면 이야기 해주셨으면 합니다.

winner의 이미지

void * 를 가지고 함수 pointer 까지 담는 것을 보고 놀랬더랬습니다.

하지만 type 안정성을 보장하지 못한다는 것이 문제죠.
물론 C 는 week type 이고(표준화과정에서 조금은 strong 해졌지만) 잘만 사용한다면 문제될 것은 없겠죠.

성능에서는 타격이 올 수 있죠.
특히 int, double 같은 built-in(Java 에서는 primitive 라는 말을 더 자주 사용하더군요.) type 같은 경우 특히 심하죠.

물론 template 에 의해 심각한 code bloat 현상이 일어났을 경우는 오히려 성능이 나아지기도 하겠지만 말입니다.

이래저래 void * 는 자주 사용하기가 쉽지 않을 것 같습니다.

template 에 비해 장점이 있다면 우선 code bloat 현상이 없을 거라는 것.
(C++ 는 code bloat 현상을 피할 수 있는 장치가 있습니다.)
object code 를 만들 수 있으므로 compile 시간의 단축, source 의 은폐가 가능하겠죠.

예전에 전웅씨의 homepage 에 물어본 바에 의하면 C 에 template 을 추가하는 것은 paradigm 의 확장을 의미하기 때문에 그런 일은 없을 거라고 하더군요.

C 표준화 위원회의 원칙 중 하나가 "C 는 C++ 가 되지 않는다." 였으니까요.

snaiper의 이미지

void ..아주 나쁜 선택은 아닙니다만..템플릿보다는 못하다고 생각합니다.
void*를 선택하는 것은 즉 형 안정성을 포기하는 것과 같습니다.
어떠한 형이 들어가도 모두 ok 니까요.

이런 점은 자바나 C# 에서도 마찬가지인데..둘 다 Object 라는 최상위 부모 클래스가 그런 역활을 합니다.
이런 구조에서 링크드 리스트 같은 컨테이너 클래스들을 보자면 모두 Object 형을 받고 리턴할 떄도 Object 형으로 리턴하도록 되어 있습니다. 그래서 원하는 형으로 캐스팅해야 되지요.

뭐 캐스팅 하는것까지 좋습니다만..이런 상속 기반 컨테이너 구성에서의 문제점은 이종 타입의 데이터가 들어가는 것을 막지 못한 다는 겁니다. int 형의 링크드 리스트다 라고 해서 선언해놨는데...float 나 short 형을 집어넣어도
에러 안 낸다는 말이죠.

void* 도 이와 마찬가지입니다. 이종 타입이 입력되는 것을 막을 수 있냐는 게 주 포인트가 되겠지요. 물론 사이즈 체크하고 뭐 이런 작업을 더 한다면 어느 정도는 막을 수 있겠지만 완벽한 해결책은 아닐 겁니다. void* 보다 더 좋은 그런게 있어야 할 듯 싶군요. boost 라이브러리의 any 처럼..union을 지향하지만 형 안정성은 추구하는 그런 라이브러리 같은걸 C에서 만들어 내면 될까 하는 생각도 듭니다.

뭐 C는 그렇다 치고 이런 이유들 떄문인지 인지 몰라도 Java, C# 의 최신 버전에는 Generic 지원이 들어가더군요. 도입한 이유 또한 설명했던 그러한 이유들을 언급하고 있지요.
그래서 제 생각으로는 C에서는 그러한 선택은 큰 문제는 없을지는 몰라도 궁극적인 해결책은 아니라고 보여집니다.

아 Generic 지원 얘기하니 이런 말이 하고 싶어지는군요. 꼭 언어는 쳇바퀴 도는 것 처럼 돌고 도는것 같습니다. Java는 C++ 단순화를 외치며 나왔고, C#은 너무 단순화 시켰다고 조금 추가시키고 좀 더 강력한(위험한?) 기능을 집어넣었습니다. 그런데 Generic 추가시킨다고 하고보니 결국 복잡한 C++ 로 돌아오는게 아닌가 싶군요. 어떻게 보면 좀 웃긴 다고 할까요?
이번에 PDC에서 나온 자료들을 좀 보자니 MC++ 에도 Generic 지원 추가한거 같던데(VB.NET에도 있는거보니 CLS에다 집어넣어버리는 것 같군요) 기존 C++ 문법을 무시하고 다른 문법으로 나가더군요. 잘못 봤는지는 모르겠으나.. 이렇게 되니... MC++ 보다는 다른 이름으로 명명하는 것이 낫지 않나 싶더군요

corba의 이미지

data블럭의 할당 및 해제를 프로그래머가 책임쳐햐 하는 부담이 있을 것 같습니다.

그리고 템플릿은 자료구조 용도 외에도 많은 곳에 이용되더군요.

template meta-programming, compile-time assertion, expression template, type list 등등...

대부분 컴파일타임에 일어나는 마법들이죠 :D

jj의 이미지

위에서 많은 분들이 지적하셨듯이.

C의 간결함도 좋지만, C의 void*, Java, C#의 Object 기반의 해결법이 가지는 가장 큰 문제점은 동적 안정성이 보장 안된다는 것이지요. 예를 들면, 재사용 가능한 동형타입리스트를 만들 수 없는것...

결국 자바나 C#도 템플릿을 확장 하지 않을 수 없는 이유겠죠.

JDK 1.6 Tiger부터는 추가되어 나온다니 기대하시길...

--
Life is short. damn short...

pyrasis의 이미지

winner wrote:
void * 를 가지고 함수 pointer 까지 담는 것을 보고 놀랬더랬습니다.

void *에 함수 포인터를 담는 간단한 예제를 알려주셧으면 좋겠습니다.

요즘 함수 포인터를 많이 보게 되는데 아직 제데로 응용하고 있는지 모르겠습니다.

winner의 이미지

제 잘못입니다.

잘못된 방식이군요.

그렇게 사용하는 것에 대해서는
http://bbs.kldp.org/viewtopic.php?t=26581

을 봐서 그런 것이 있는 줄 알았구요.

찾아보니 자세한 source 가 없어서 han.comp.lang.c 를 찾아보니
[url]http://groups.google.co.kr/groups?hl=ko&lr=&ie=UTF-8&inlang=ko&newwindow=1&threadm=962lr7%24esb%241%40nnrp1.deja.com&rnum=8&prev=/groups%3Fq%3Dvoid%2B*%2Bgroup:han.comp.lang.c%26hl%3Dko%26lr%3D%26ie%3DUTF-8%26inlang%3Dko%26newwindow%3D1%26group%3Dhan.comp.lang.c%26selm%3D962lr7%2524esb%25241%2540nnrp1.deja.com%26rnum%3D8[/url]

이 있었습니다.

void * 는 대상체(표준문서에서는 object 라고 기술되어 있다는데 Lvalue 와 같은 개념이라더군요.) pointer 의 일반형만 될 수 있다는군요.

grassman의 이미지

Implementation-defined로 정의되기는 했지만 내용은 있군요.

J.5.7 Function pointer casts
 
1 A pointer to an object or to void may be cast to a pointer to a function, allowing data to
  be invoked as a function (6.5.4).
 
2 A pointer to a function may be cast to a pointer to an object or to void, allowing a
  function to be inspected or modified (for example, by a debugger) (6.5.4).

동작 하드웨어에 따라 위험한 동작을 할 수도 있지만 실제로는 사용할 수 있는 방법이라고 생각합니다.
(포인터를 사용하는 것 자체가 하드웨어에 대한 이해를 요구하기도 하므로.. 라고 정당화 해 봅니다)

이한길의 이미지

winner wrote:
성능에서는 타격이 올 수 있죠.
특히 int, double 같은 built-in(Java 에서는 primitive 라는 말을 더 자주 사용하더군요.) type 같은 경우 특히 심하죠

성능 타격에 대해 구체적으로 지적해주셨으면 감사하겠습니다.

snaiper wrote:
void*를 선택하는 것은 즉 형 안정성을 포기하는 것과 같습니다.
어떠한 형이 들어가도 모두 ok 니까요.

맞습니다. 하지만 그래도 아쉬운데로 괜찮지 않을까요? 일반화하기 위해 사용하는데 그 외의 대안이 있을런지 모르겠습니다. 예전에 C기초 플러스 책에서 ADT라는 주제가 있었던것 같긴한데 제대로 읽질 않아서 모르겠습니다.

corba wrote:
template meta-programming, compile-time assertion, expression template, type list 등등...

제가 아직 C++에 대해 잘 몰라서 위의 내용들은 모르겠습니다만 주로 template는 일반화하기 위해 사용하지 않나요? 저는 그 부분에 대해서 생각을 하고 있던지라.. 구체적으로 설명을 조금만 덧붙여 주셨으면 좋겠습니다.

----
먼저 알게 된 것을 알려주는 것은 즐거운 일이다!
http://hangulee.springnote.com
http://hangulee.egloos.com

onemind555의 이미지

Quote:
알고리즘을 만들때..
각각의 자료형 마다 알고리즘을 새로 작성 해야 하는 번거러움을 피하기 위해 사용 하는 기법..

1 . C에서는 void * 형태를 사용한다.

2. 자바에서는 Object 클래스를 이용 한다.

3. C++에서 템플릿을 사용 한다.

템플릿이 최고 낫다. 1 번 2번은 문제가 있다.

저런 내용을 어느 책에선가 본 것 같은데.... 기억이 ....
[/code]

-----------^^ ^^ ^^ ^^ ^^ ----------
..........................................................

winner의 이미지

함수 pointer 에 대한 윗글과 함께 찾은 글
[url]http://groups.google.co.kr/groups?hl=ko&lr=&ie=UTF-8&inlang=ko&newwindow=1&threadm=9soik6%24i48%241%40news1.kornet.net&rnum=74&prev=/groups%3Fq%3Dvoid%2B*%2Bgroup:han.comp.lang.c%2Bgroup:han.comp.lang.c%2Bgroup:han.comp.lang.c%2Bgroup:han.comp.lang.c%2Bgroup:han.comp.lang.c%2Bgroup:han.comp.lang.c%2Bgroup:han.comp.lang.c%2Bgroup:han.comp.lang.c%26start%3D70%26hl%3Dko%26lr%3D%26ie%3DUTF-8%26inlang%3Dko%26newwindow%3D1%26group%3Dhan.comp.lang.c%26selm%3D9soik6%2524i48%25241%2540news1.kornet.net%26rnum%3D74[/url]

성능타격이 실제적으로 이루어지는 것으로 Scott Meyers 의 Effective STL 에서는 double 을 가지고(몇 개를 썼는지는 기억이 안납니다... 죄송.) 정렬하는 실험을 했습니다.

결과는 C 의 표준 library 의 qsort 와 6~7배 차이가 났습니다.

Jon Bently 의 Programming Pearls(번역 : 생각하는 Programming) 에서는 정수 백만개를 가지고 실험했는데 약 4배 차이를 냈습니다.

void * 를 사용해서 일반성을 갖추면서 code 를 만들려면 함수 pointer 를 사용하는 수 밖에는 없을 것입니다. 함수를 가지고 비교하기 때문에 built-in type 의 경우 실제적인 기계어와 1:1 matching 이 가능한 연산자 비교와 차이가 심하게 날 수 밖에 없습니다.

제 기억이 정확한지 모르겠는데 Effective STL 에서는 double 을 비교하는 함수객체를 썼습니다. 하지만 inlining 과 함께 추가적인 최적화로 인해 연산자비교와 다름없는 결과를 냈었을 거라고 봅니다.

C 의 표준 library 의 qsort 는 간단히 쓰는데에는 괜찮지만 성능을 올리고 싶다면 피해야 하는 대상으로 보더군요.

http://bbs.kldp.org/viewtopic.php?t=4201

winner의 이미지

template meta-programming, compile-time assertion, expression template, type list 등등에 관해서는

Modern C++ Design
http://www.infopub.co.kr/common/bookinfo/bookinfo.asp?sku=06000142 을 보세요.

내용은 C++ 를 사용한 Design Patterns 접근에 관한 것인데(그래서 Template Meta Programming 이라고 하죠.) Design Patterns 외에도 C++ 에 대한 심도있는 내용으로 흥미로운 이야기가 많습니다... 위의 나머지 부분에 해당하죠.

추천사를 쓴 사람이 GoF 중 한사람인 John Vlissides 와 Effective Series 의 Scott Meyers 였습니다.

저도 보고 싶었는데 펼치니까 전혀 알 수 없어서 포기했더랬습니다. TT
Design Patterns 는 전혀 모르고 아직은 알고 싶지 않아서... -_-.
아~ 그렇다고 Design Patterns 를 읽고 나서 읽어야 하는 책은 아닙니다.
오히려 난해한 Design Patterns 에 비해 실체적인 접근을 해서 유용하다고 하더군요.

Template Meta Programming 때문에 Pattern 이 과연 실제적인 code 로 표현이 가능한 것인가에 대한 논란이 일어나게 만들기도 했다고 합니다.
결론은 Template 은 실제적인 code 가 아니라 형판이라나?...

포기하기 전에 대충 봤던 것 중 기억나는 것이 new, delete 는 malloc, free 에 비해서 상당히 느리다였습니다. 그리고 그 이유는 new 를 그냥 malloc 의 wrapping 으로 작성한 경우가 많기 때문이라고 하더군요.

사실 new 는 type 의존적이기 때문에 배열이 아니라면 compile 때 모든 것이 결정되므로 오히려 성능이 올라갈 여지가 있는데도 말이죠.

corba의 이미지

hangulee wrote:

corba wrote:
template meta-programming, compile-time assertion, expression template, type list 등등...

제가 아직 C++에 대해 잘 몰라서 위의 내용들은 모르겠습니다만 주로 template는 일반화하기 위해 사용하지 않나요? 저는 그 부분에 대해서 생각을 하고 있던지라.. 구체적으로 설명을 조금만 덧붙여 주셨으면 좋겠습니다.

사실 저도 아직 C++은 미숙하지만 위에 예시된 것들은 꽤나 흥미로운 주제였습니다.
허접한 제 실력으로 설명이 가능할진 모르겠지만 답변을 올린 책임을 지고 최대한 열심히 해보겠습니다. :oops:

우선 template meta-programming과 expression template은 96년도 DDJ에서 기사를 본 적이 있었습니다.

template meta-programming은 런타임엔 실행이 안되고 컴파일타임에만 실행되는 템플릿의 특성을 이용해서 프로그래밍을 하는 것인데 재귀호출과 enum의 트릭, 그리고 템플릿 특화를 주로 이용하는 것 같습니다.
템플릿 메타프로그래밍의 수행시간은 컴파일타임에만 일어납니다.
꽤 신기하긴 합니다만 범용적으로 사용하긴 힘든 기술입니다.
근데 이걸로 컴파일 타임 소트도 만들더군요. :D

expression template은 역시 템플릿의 특화기능을 이용한 트릭으로 수식을 파싱해서 컴파일타임에 연산을 시키는 기술이라고 알고 있습니다.
이것을 이용해서 복소수연산 라이브러리를 만들어서 꽤나 빠른 속도를 냈다죠 아마...

compile-time assertion은 템플릿 특화를 이용해서 말그대로 compile-time에 assertion을 발생시키는 기술입니다.
역시 범용적으론 사용하지 못하고 주로 의도치 않은 묵시적 형변환 등을 막기 위해서 사용된다고 합니다.
흔히 하는 런타임 널포인터 체크 이런건 힘들겠지요.
compile-time assertion이 걸리면 컴파일이 안되므로 고치지 않을 수가 없게 됩니다. :wink:

그리고 마지막으로 괴물같은 type list...
한마디로 이걸 첨봤을때 충격이 엄청났습니다.
어떻게 이런 생각을 해냈는지가 궁금합니다.
이것은 말 그대로 타입의 리스트입니다.
[int, int, float]의 식으로 생각하시면 되겠네요...
이것을 이용하면 튜플이나 다이나믹한(어디까지나 컴파일 타임이지만)파라미터 리스트 같은 것들을 구현할 수 있습니다.

저도 아직 공부하고 있는 처지라 자세히 설명 못드린점 죄송합니다.
C++ Template와 Modern C++ Design이란 책을 참고하시면 좋을 듯 합니다.

youlsa의 이미지

제가 보기에는 C++의 템플릿을 대치한다기 보다는 OOP를 흉내내는 방법인
것 같습니다. GTK+같은 프로젝트에서 그런 방법으로 객체지향을 구현하고
있습니다. C프로그래머들이 자주 쓰는 방법이지요. 커널 2.6에서의 디바이스
드라이버들도 똑같지는 않지만 이런 방식으로 프로그래밍 하도록 바뀌었습니
다. 기본적인 원리에 대한건
OOP with ANSI-C라는 글을 한번 보시길 권합니다. 근데 저게 독일어를 기계
번역해놓은거라 소스가 이상하게 나옵니다. 소스는 원문을 보시면 됩니다.

간단하게 말로 설명을 하자면 클래스에 대한 정보를 공통(베이스) 구조체에
담아놓고 런타임에는 void*형을 베이스 클래스형으로
타입캐스팅해서 해당 정보를 읽어들여서 클래스의 종류를 파악합니다. 물론
생성자에서 정성스레 함수 포인터들로 함수 테이블을 채워주어야 합니다. 8)
저도 C로 프로그래밍 해야할 때에 이 방식을 쓰는데 스택, 링크드리스트, 더블
링크드 리스트, 딕셔너리 정도 까지 만들어 놓은 싸제(?)라이브러리를 사용하는데
의외로 편리합니다. 데이타 형에서 자유롭지요. 스택 안에 스트링이건 링크드
리스트건 마구 때려넣을 수도 있고 딕셔너리의 키가 스트링인데 밸류 값으로
링크드 리스트나 딕셔너리를 넣을 수도 있고 최상위 오브젝트
에서 파생된 아무 오브젝트나 다 넣을 수 있습니다. 몇몇 기본 라이브러리만
구축되어 있으면 java나 python의 개발 속도가 안부러울 정도지요. 적당히
쓰기 편한데 까지만 심플화한 객체지향 방법을 이용하는겁니다. :D

=-=-=-=-=-=-=-=-=
http://youlsa.com

이한길의 이미지

onemind555 wrote:
Quote:
알고리즘을 만들때..
각각의 자료형 마다 알고리즘을 새로 작성 해야 하는 번거러움을 피하기 위해 사용 하는 기법..

1 . C에서는 void * 형태를 사용한다.

2. 자바에서는 Object 클래스를 이용 한다.

3. C++에서 템플릿을 사용 한다.

템플릿이 최고 낫다. 1 번 2번은 문제가 있다.

저런 내용을 어느 책에선가 본 것 같은데.... 기억이 ....
[/code]

저는 이렇게 생각하지 않았었는데... 많은 분들이 그렇게 생각을 하고 계시는 것 같더군요. 사실 일반화를 위해 위의 세가지를 사용한다면 template에 비해 1, 2번은 문제가 있지요.

일단 template는 컴파일시 정해진 것에 맞는지 type 확인을 해주지요. 하지만 나머지 두개는 집어 넣을 수 있는 type에 제한이 없습니다. 그렇기 때문에 확인을 하지 않습니다. 그리고 정해줄 수 있는 방법도 없구요. 처음부터 같은 용도를 위해 만든것이 아니기 때문에 어쩔 수 없지 않나 싶습니다.

아울러 Object를 상속하는 것은 OOP의 특성을 이용한 것이라고 생각됩니다. 저는 이것역시 괜찮은 방법이라고 생각하고 있었는데 왜냐하면 하나의 개념을 가지고 깔끔하게 원하는 처리를 할 수 있다는 생각 때문이었습니다.

winner wrote:
성능타격이 실제적으로 이루어지는 것으로 Scott Meyers 의 Effective STL 에서는 double 을 가지고(몇 개를 썼는지는 기억이 안납니다... 죄송.) 정렬하는 실험을 했습니다.

결과는 C 의 표준 library 의 qsort 와 6~7배 차이가 났습니다.

Jon Bently 의 Programming Pearls(번역 : 생각하는 Programming) 에서는 정수 백만개를 가지고 실험했는데 약 4배 차이를 냈습니다.

void * 를 사용해서 일반성을 갖추면서 code 를 만들려면 함수 pointer 를 사용하는 수 밖에는 없을 것입니다. 함수를 가지고 비교하기 때문에 built-in type 의 경우 실제적인 기계어와 1:1 matching 이 가능한 연산자 비교와 차이가 심하게 날 수 밖에 없습니다.

C++의 template와 비교한 것인가요? 아무튼 C++에서나 C에서나 포인터는 자주 사용할 수 밖에 없습니다. 제가 생각할때 void는 임의의 type이라고 하는데 이것은 컴파일러에서 체크를 하지 않고 사용할 수 있기 때문인것 같습니다. 앞에 링크해주신 글에서 읽어본것 같은데 전에는 char가 void를 대신했었다고 하는군요. 생각해보면 메모리상에 어떻게 저장되느냐의 차이인데 void가 그 주소를 가지고 있는것과 int나 그밖의 type들이 주소를 가지고 있는것에는 큰 차이가 없다고 생각하는데요... 다시한번 확인해봐야겠습니다. 자료를 제시해 주셔서 감사합니다.

winner님과 corba님이 같은 책을 추천해주신것 같네요. 기회다 닿으면 참고해봐야겠습니다. 그리고 설명도 감사드립니다.

youlsa wrote:
제가 보기에는 C++의 템플릿을 대치한다기 보다는 OOP를 흉내내는 방법인
것 같습니다. GTK+같은 프로젝트에서 그런 방법으로 객체지향을 구현하고
있습니다. C프로그래머들이 자주 쓰는 방법이지요. 커널 2.6에서의 디바이스
드라이버들도 똑같지는 않지만 이런 방식으로 프로그래밍 하도록 바뀌었습니
다. 기본적인 원리에 대한건
OOP with ANSI-C라는 글을 한번 보시길 권합니다. 근데 저게 독일어를 기계
번역해놓은거라 소스가 이상하게 나옵니다. 소스는 원문을 보시면 됩니다.

좋은 글 추천해주셔서 감사합니다. 사실은 저도 GTK+의 소스를 보면서 C로 객체지향프로그래밍을 하는 것에 대해 다시 생각해보게 되었습니다.

----
먼저 알게 된 것을 알려주는 것은 즐거운 일이다!
http://hangulee.springnote.com
http://hangulee.egloos.com

죠커의 이미지

winner wrote:
성능타격이 실제적으로 이루어지는 것으로 Scott Meyers 의 Effective STL 에서는 double 을 가지고(몇 개를 썼는지는 기억이 안납니다... 죄송.) 정렬하는 실험을 했습니다.

결과는 C 의 표준 library 의 qsort 와 6~7배 차이가 났습니다.

Jon Bently 의 Programming Pearls(번역 : 생각하는 Programming) 에서는 정수 백만개를 가지고 실험했는데 약 4배 차이를 냈습니다.

void * 를 사용해서 일반성을 갖추면서 code 를 만들려면 함수 pointer 를 사용하는 수 밖에는 없을 것입니다. 함수를 가지고 비교하기 때문에 built-in type 의 경우 실제적인 기계어와 1:1 matching 이 가능한 연산자 비교와 차이가 심하게 날 수 밖에 없습니다.

이펙티브 STL의 테스트 라면 void *이기 때문에 성능이 떨어지는 것을 테스트 하는 것이 아니라 함수 포인터와 함수 객체의 성능 비교입니다. 비교의 대상이 잘못되었다고 생각이 듭니다. qsort는 함수 포인터를 받아들였고 stl의 sort는 함수 객체를 받아들여서 테스트 했습니다.

함수 객체는 operator()를 재정의 하는 것 만큼 만들기는 귀찮습니다만 부피는 없습니다. 그래서 컴파일러는 그 메소드가 실제로 쓰이는 부분에 inline해버립니다. 포인터로 호출되는 것보다 당연히 매끄럽게 포함된 코드가 성능이 뛰어나기 마련이며 그래서 성능 차이가 나는 것입니다.

wind772의 이미지

ㅎㅎ 제가 만든 허접한 소스지만...
n개의 스택을 만들어 사용하는 소스입니다.
크기는 각각 스택별로 설정할수 있고요..^^;;
버그는...장담 못함...(*__)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct _StAcK_DaTa_ {
        void *                  data;
        struct _StAcK_DaTa_ *   prev;
        struct _StAcK_DaTa_ *   next;
} StAcK_DaTa;

static StAcK_DaTa **    StAcK_DaTa_Head  = NULL;
static StAcK_DaTa **    StAcK_DaTa_Top   = NULL;
static int*             StAcK_DaTa_Size  = NULL;
static int              StAcK_DaTa_Limit = 0;

void Stack_Make(int i)
{
        StAcK_DaTa_Head = (StAcK_DaTa**)malloc(sizeof(StAcK_DaTa*)*i);
        memset(StAcK_DaTa_Head, 0, sizeof(StAcK_DaTa*)*i);

        StAcK_DaTa_Top = (StAcK_DaTa**)malloc(sizeof(StAcK_DaTa*)*i);
        memset(StAcK_DaTa_Top, 0, sizeof(StAcK_DaTa*)*i);

        StAcK_DaTa_Size = (int*)malloc(sizeof(int)*i);
        memset(StAcK_DaTa_Size, 0, sizeof(int)*i);

        StAcK_DaTa_Limit = i;
}

void Stack_Close()
{
        int i;
        for(i=0 ; i<StAcK_DaTa_Limit ; i++)
                if(StAcK_DaTa_Head[i] != NULL)
                {
                        StAcK_DaTa *s, *n;
                        s = StAcK_DaTa_Head[i];
                        while( s != NULL) {
                                n = s->next;
                                free(s->data);
                                free(s);
                                s = n;
                        }
                }

        free(StAcK_DaTa_Size);

        StAcK_DaTa_Head  = NULL;
        StAcK_DaTa_Top   = NULL;
        StAcK_DaTa_Size  = NULL;
        StAcK_DaTa_Limit = 0;
}

void Stack_Init(int i, size_t size)
{
        if(i>=StAcK_DaTa_Limit)
        {
                fprintf(stderr, "Stack Number Error!! : Using Stack Number %d\n", i);
                return;
        }

        if(StAcK_DaTa_Head[i] != NULL)
        {
                StAcK_DaTa *s, *n;
                s = StAcK_DaTa_Head[i];
                while(s != NULL)
                {
                        n = s->next;
                        free(s->data);
                        free(s);
                        s = n;
                }
        }

        StAcK_DaTa_Size[i] = size;
        StAcK_DaTa_Head[i] = (StAcK_DaTa*)malloc(sizeof(StAcK_DaTa));

        StAcK_DaTa_Head[i]->data = (void*)malloc(StAcK_DaTa_Size[i]);
        memset(StAcK_DaTa_Head[i]->data, 0, StAcK_DaTa_Size[i]);
        StAcK_DaTa_Head[i]->prev = NULL;
        StAcK_DaTa_Head[i]->next = NULL;

        StAcK_DaTa_Top[i] = StAcK_DaTa_Head[i];
}

void Stack_Push(int i, void* data)
{
        StAcK_DaTa * s;

        if(i>=StAcK_DaTa_Limit)
        {
                fprintf(stderr, "Stack Number Error!! : Using Stack Number %d\n", i);
                return;
        }

        memcpy(StAcK_DaTa_Top[i]->data, data, StAcK_DaTa_Size[i]);

        s = (StAcK_DaTa*)malloc(sizeof(StAcK_DaTa));
        s->data = (void*)malloc(StAcK_DaTa_Size[i]);
        memset(s->data, 0, StAcK_DaTa_Size[i]);
        s->prev = StAcK_DaTa_Top[i];
        s->next = NULL;

        StAcK_DaTa_Top[i]->next = s;
        StAcK_DaTa_Top[i]       = s;
}

void Stack_Pop(int i, void* data)
{
        StAcK_DaTa temp;

        if(i>=StAcK_DaTa_Limit)
        {
                fprintf(stderr, "Stack Number Error!! : Using Stack Number %d\n", i);
                memset(data, 0, StAcK_DaTa_Size[i]);
                return;
        }

        if(StAcK_DaTa_Head[i] == StAcK_DaTa_Top[i])
        {
                memset(data, 0, StAcK_DaTa_Size[i]);
                return;
        }

        memcpy(&temp, StAcK_DaTa_Top[i], sizeof(StAcK_DaTa));
        free(StAcK_DaTa_Top[i]);
        StAcK_DaTa_Top[i]       = temp.prev;
        StAcK_DaTa_Top[i]->next = NULL;

        if(data>0)
                memcpy(data, StAcK_DaTa_Top[i]->data, StAcK_DaTa_Size[i]);
}

int Stack_Empty(int i)
{
        if(i>=StAcK_DaTa_Limit)
        {
                fprintf(stderr, "Stack Number Error!! : Using Stack Number %d\n", i);
                return -1;
        }

        return (StAcK_DaTa_Head[i] == StAcK_DaTa_Top[i]);
}

===================================================
중요한건 얼마나 아느냐가 아니라 그것에 대한 열정이다.

chunsj의 이미지

제 생각엔 그 사용목적에 따라 다를 것이라고 생각됩니다. 만약 void* 라는 것을
쓰는 이유가 새로운 타입과 같은 것을 생성하기 위한 것이라면 Template이
더 좋은 선택이라고 생각됩니다. 적절한 인라이닝과 같이 쓰면 성능도(
정적 타입을 선택했으니, 안정성/성능이 최고의 목적이 되어야 겠지요) 크게
해치지 않고 안정성도 담보할 수 있다고 생각됩니다.
만약 다양한 타입의 데이터를 다루고 싶다면, 그리고 언어가 제한이 되어
있다면 void pointer가 좋은 선택이라고 생각됩니다. 언어의 제한이 없다면
동적인 언어인 ObjC가 최선이 되겠지요. C와의 완벽한 호환도 보장되고...

hangulee wrote:
아래 "C++은 C의 확장?"라는 괜찮은 토론이 있었습니다. 하지만 조금 주제가 넓어서 그런지 흐름이 이리저리 움직이는 것 같네요. 조금 구체적인 이야기를 하고 싶어지는군요.

jj wrote:
개인적으로 C에 템플릿만 추가된다면, 시스템 프로그래머는 더이상 바랄것이 없을것 같습니다.

위 토론중에 jj님이 이런 이야기를 하셨습니다. 물론 template라는 C++의 훌륭한 기능이 C에 추가되는 것도 괜찮겠지만 가만히 생각해보면 C에서는 void라는 type을 사용하는 것이 더 적합하지 않나 싶습니다. 물론 같은 기능을 하는것은 아니지만 그럭저럭 잘 사용할 수 있지요. 간단한 예를 보면..

int x=10;
void *t=&x;
int *y=(int *)t;

printf("%d\n", x);
printf("%d\n", *(int *)t);
printf("%d\n", *y);

알 수 있듯이 모두 10이라는 결과를 출력합니다.

void라는 type은 위의 예처럼 int를 담을 수도 있지만 다른 type도 담을 수 있습니다. 보통 template를 사용하는 경우에는 기본적인 동작은 동일하지만 그 외의 다른 필드들이 사용되는 경우 유용하지요. 예를 든다면 링크드 리스트같은 것이 있겠지요. 하지만 링크드 리스트의 구조를 다음과 같이 정의하면 어떨까요?

struct _linkedlist_
{
  void *data;
  struct _linkedlist_ *next;
};

그리고 data에는 필요한 다른 구조체를 정의해서 집어 넣어 사용하는 것입니다. 괜찮은 생각이 아닐까 싶은데요... 다른 경험들 있으시면 이야기 해주셨으면 합니다.

winner의 이미지

void * 처럼 형 안정성을 보장하지 못하는데다가 coding 하기도 까다롭고 남용시 code 비대화가 발생할 수 있긴 합니다만 잘 쓰면 역시 유용하죠.
"C 의 표준화 원칙 중 하나가 Programmer 를 믿는다."였나요?
어떻게 저같은 Programmer 를 믿을 수 있는지... (-_-)

template 은 사실 OOP 의 상속구조와 중복되는 점이 있기는 합니다.
그런데도 Generic Programming 과 STL 은 함수위주라는 점이 특이하죠.
두가치를 처음 제시한 Alexander Stephanov 는 "모든 것은 객체다"라는 말은 그 어느 것도 설명해주지 못한다면서 OOP 와 Java 를 혹평한 것으로 아는데...

재사용이라는 것도 다른 방식으로 구현할 수 있다는 것이 특이하죠.

제가 생각하기에는 built-in type 을 제외하고 template 이 OOP 에 비해 얼마나 이득이 있는지 궁금하네요.

하지만 저는 역시 Java 의 primitive type Wrapper Class 가 맘에 안듭니다.

순수 OOP 를 지향한다면 template 은 필요없을 겁니다. 실제로 객체지향script 언어들이 그렇고, Smalltalk 도 그렇죠.

chunsj의 이미지

Template이 라는 것이 애시당초 다이나믹한 특성을 가진 순수 OOP를 위해서
나온 것이 아니라 정적인 언어들의 문제를 위해서 나온 것이기 때문이죠.
그리고 이런 정적인 특성은 상대적으로 안정성에 대해서 좀 더 나을 것이라고
보고 나온 것이죠.
변화에 대한 적응력을 포기하고 안정성을 위해서 만든 특성이므로 적어도
그점에 있어서는 좀 더 나아야 하지 않겠습니까? :-)

winner wrote:
void * 처럼 형 안정성을 보장하지 못하는데다가 coding 하기도 까다롭고 남용시 code 비대화가 발생할 수 있긴 합니다만 잘 쓰면 역시 유용하죠.
"C 의 표준화 원칙 중 하나가 Programmer 를 믿는다."였나요?
어떻게 저같은 Programmer 를 믿을 수 있는지... (-_-)

template 은 사실 OOP 의 상속구조와 중복되는 점이 있기는 합니다.
그런데도 Generic Programming 과 STL 은 함수위주라는 점이 특이하죠.
두가치를 처음 제시한 Alexander Stephanov 는 "모든 것은 객체다"라는 말은 그 어느 것도 설명해주지 못한다면서 OOP 와 Java 를 혹평한 것으로 아는데...

재사용이라는 것도 다른 방식으로 구현할 수 있다는 것이 특이하죠.

제가 생각하기에는 built-in type 을 제외하고 template 이 OOP 에 비해 얼마나 이득이 있는지 궁금하네요.

하지만 저는 역시 Java 의 primitive type Wrapper Class 가 맘에 안듭니다.

순수 OOP 를 지향한다면 template 은 필요없을 겁니다. 실제로 객체지향script 언어들이 그렇고, Smalltalk 도 그렇죠.

죠커의 이미지

winner wrote:
순수 OOP 를 지향한다면 template 은 필요없을 겁니다. 실제로 객체지향script 언어들이 그렇고, Smalltalk 도 그렇죠.

java는 순수 OOP가 아니죠 :-)

순수한 OOP라면 a+b 조차 a.add(b)와 같은 방법으로 되어야 한다고 알고 있습니다.

어짜피 명령형 언어의 패러다임을 받아들이고 있는데 그 명령형 언어의 단점을 해소시킬 템플릿의 도입은 나쁘지 않다고 봅니다. :-)

netskate의 이미지


Object Oriented

Object Based

===================================================
Make it Simple, Easy, Compact !!!!

===================================================
Make it Simple, Easy, Compact !!!!

netskate의 이미지


void (*signal(int signum, void (*handler)(int)))(int);

int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg);

===================================================
Make it Simple, Easy, Compact !!!!

===================================================
Make it Simple, Easy, Compact !!!!

a287848의 이미지

커널에 있는 링크드 리스트가 생각나네요.

Macro 를 사용한 Linux Kernel의 링크드 리스트도

template 못지 않죠~

Dig it.

Dig it.

neogeo의 이미지

void* 나 Macro가 (혹은 조합이 ) template 못지 않다 라는 말을 하시기전에

modern c++ design 책이나 STL source code 를 한번이라도 보고 이야기 해주십시오.

void* 나 Macro가 template 의 일부행동을 흉내낼 수 있지만, 둘은 전혀 비교의 대상이 아닙니다.

void* 나 Macro가 커버하는 범위와 template 의 목적은 전혀 다른영역입니다.
( generic 한 programming 을 위해 template 이 시작되었다고 하더라도 말이죠. )

최근 몇년간의 template 사용법이 얼마나 다채로운가 알게되면 void* 와 template 은 전혀 그 궤를 다르게 하는 녀석이라는것을 아시게 될 것입니다.

template 의 meta programming 영역, 그리고 그 활용법( modern c++ design )을 맛 보기전에는 C++ 은

pointer 를 배우기 전의 C 를 바라보는것과 같다고 생각합니다.

Neogeo - Future is Now.

Neogeo - Future is Now.

Hyun의 이미지

Quote:
template 의 meta programming 영역, 그리고 그 활용법( modern c++ design )...

에 대해 좀 더 설명해 주실 수 있나요??
chunsj의 이미지

정적타이핑에 목숨 건 언어의 형태가 된 원죄로 인해서 템플릿이라는 오버헤드가 생기는 것입니다. 동적인 언어들 - smalltalk이나 lisp 같은 - 에서 템플릿은 불필요합니다. 실행시간에 객체/대상을 판단할 수 있고 클로저가 지원이 된다면 템플릿이 가지는 잇점은 모두 가지게 되고 추가로 더 단순하고 간단한 코드를 얻을 수도 있습니다.

물론 void*가 흉내낼 수 있는 것은 일반형에 대한 대체일 뿐 메타프로그래밍에 해당되는 부분은 아니라는데는 동의합니다.

klyx의 이미지

템플릿은 코드는 늘어날지언정 컴파일 타임에 모든게 끝나기 때문에 오히려 오버헤드는 없을텐데요...
게다가 동적인 언어가 실행시간에 객체/대상을 판단할수 있다고 하셨는데요, 오히려 반대로 생각하면 템플릿은 객체에 대한 판단을 컴파일 타임으로 가져옴으로써 런타임 에러도 방지할수 있고, (그다지 크지 않을 것이라 생각되지만) 런타임시에 확인할때 발생하는 오버해드도 줄일수 있는 효과도 있다고 생각합니다.

chunsj의 이미지

제가 말씀드린 오버헤드는 템플릿 그 자체입니다. 원래 언어가 제한이 있어서 그 제한을 돌아가기 위해서 템플릿이라는 새로운 구문을 추가한 것을 말한 것입니다.

이런 식이라면 POC와 같은 식으로 전처리컴파일러를 사용하면 훨씬 더 간단하게 같은 일을 할 수 있지요. 정적 타이핑을 가지는 대신에 - 동적 타이핑 언어는 약간의 오버헤드가 있지만 같은 일을 할 수 있는데 반하여 정적 언어는 스스로를 확장해야하는 문제가 있습니다. - 이런 문제가 생기면 케이스 별로 언어를 바꾸는 일을 해야하고 이건 언어를 사용하는데 있어 새로운 장애가 되지 않나하고 생각합니다.

neogeo의 이미지

이론적으로는 그렇게 시작한게 맞습니다.

그러나 거꾸로 지금 template 은 oop 색다른 활동성을 불어넣었습니다. 즉 바이너리가 커지는 부담을 가지지만 동적으로 해야만 할 부분을 정적으로 커버가 가능하게 만들어주었지요. ( 프로그래머에겐 동적으로 보여지게 하면서요. )

대표적인것이 loki library 라던가 tr1 등을 포함한 boost 그리고 C++0X 의 정해질 언어 스펙들입니다.

다양하게 코딩해서 상속해야했을 복잡한 object 가 template argument 등으로 여러가지 형태로 상속을 자유롭게 대체해가며 쓸 수 있는

policy class pattern 이라던가, 다양한 argument 를 처리할 수 있는 type list 라던가 등을 Loki 나 Modern C++ Design 을 통해

살펴보실 수 있습니다. template 속에 template 이 인자로 들어가거나 default 인자가 생기게 됨으로써 여러가지 확장이 가능합니다.

이러한 확장성과 새로운 패러다임은 단순한 전처리기로는 불가능 합니다. 컴파일 타임에 OOP 개념을 넣었다고 해야할까요?

( 부분 특수화나 전체 특수화, template default 인자 등등 여러가지 문제 때문에 c++ 표준화가 오래 걸리기도 했지요. )

boost 가 주는 generic 한 장점 또한 매우 뛰어나기도 하구요. ( Generic 한 알고리즘을 C 언어에 가까울 정도로 속도를 포기하지 않으면서 사용할 수 있지요. 대표적인것이 C 언어의 qsort 와 C++ 의 sort 의 차이입니다. c++ 의 sort 는 compile time 에 일반적으로
최적화 될 확률이 매우 커서 C 언어 같이 void 나 function pointer 로 캐스팅 하는 것보다 더 빠르게 될 확률이 높습니다. ( Effective c++ 책을 참고해보세요 ) 더 크게 생각해보면 generic 한 알고리즘들을 구현하기엔 C++ 이 더 좋을 수 있다는 점입니다. 물론 특정한 행동을 하는것은 C가 더 빠를 확률이 높습니다. )

c++0X 부분을 제대로 다 포함하게 된다면 C++ 의 template 이 주는 새로운 방식에 깜짝 놀라게 됩니다.

( 모양만 이긴 하지만 functional language 의 일부 수식형태도 흉내 낼 수 있게 됩니다. 이미 VC++ 2008 sp1 은 tr1 을 포함했습니다. )

Neogeo - Future is Now.

Neogeo - Future is Now.

winner의 이미지

STL 좋아하는 저도 template이 동작하는 방식에 대해서 의문을 가질 때가 많습니다.
도대체 STL을 내가 어떻게 쓰고 있는지 의문... -_-.

imyejin의 이미지

템플릿
- 단점: 제대로 만들려면 X나게 삽질해야 한다
- 장점: 누가 한번만 고생하면 다른 사람이 편하고 안전하게 살 수 있다 (boost ~~~)

임예진 팬클럽 ♡예진아씨♡ http://cafe.daum.net/imyejin

[예진아씨 피카사 웹앨범] 임예진 팬클럽 ♡예진아씨♡ http://cafe.daum.net/imyejin

wish의 이미지

Quote:

다양하게 코딩해서 상속해야했을 복잡한 object가 template argument 등으로 여러가지 형태로 상속을 자유롭게 대체해가며 쓸 수 있는 policy class pattern 이라던가,

저는 이부분 이해 불가능입니다. object가 주어인 것 같긴 한데, object가 상속을 대체한다는 의미는 아닌 것으로 보이는데.

문맥으로 추측하건데, "원래라면 복잡한 상속관계를 가져야 했을 object도 policy class pattern을 이용하면 "정적으로" 정의 가능하다. policy class pattern은 template argument등을 이용해서 여러가지 형태로 상속을 자유롭게 대체할 수 있는 pattern이다." 이런 뜻일까요?

그리고 네오지오님께서 하시는 말씀도 결국은 "동적인 것 중 많은 부분이 정적으로 가능하다" 정도로 보이고, 따라서 chunsj 님께서 하신 말씀과 크게 다르지 않다고 생각합니다.

그리고 아무리 읽어봐도 "새로운 패러다임"에 해당하는 건 없는 것 같습니다. "컴파일 타임에 OOP 개념을 넣었다"는 표현에는 무지 공감합니다만, 그런걸 "새로운 패러다임"이라고 하는 건 과장되 보입니다. 오히려 정적인 환경에서도 이런걸 할 수 있다는 걸 보여주었다는 느낌입니다.

neogeo의 이미지

말을 제가 좀 복잡하게 썼군요 죄송합니다. 이해하신 대로가 맞습니다.

정적인 환경에서 그런게 되었다는 자체가 중요한게 아니라 프로그래머에겐 동적으로 보이게 하면서 정적으로 처리가 되는 상황을 만들었다는게 새로운 패러다임이라는 것입니다.

이러한 것이 주는 속도의 장점이 새로운 패러다임이라고 말 했던 것입니다.

하지만 저도 새로운 패러다임이라는 표현이 틀린표현이라고 생각되는군요.

새로운 패러다임이라기보단 다른 방식으로 동적인것을 지원한다라고 보면 되겠네요.

다만 이 부분이 다른 그 어떤 언어에서도 보거나 상상하기 힘든 장점을 제공했다고 생각합니다.

위에 말씀드린대로 qsort 와 c++ 의 sort 의 속도 차이가 대표적인 예가 되겠습니다.

( 특히 컴파일 타임에 최적화를 당길 여지가 무지하게 많아졌습니다. 이런 점은 정말 새로운 시점으로 접근해야합니다. )

문법이나 이해하기는 난해하지만, generic 성과 performance 를 다 버리지 않으면서 양쪽을 동시에 추구할 수 있다는 철학이 담겨있기때문에

새로운 개념이라고 생각하는 것 입니다.

Neogeo - Future is Now.

Neogeo - Future is Now.

NN의 이미지

void * 와 템플릿간 비교는 기능상 비슷한 점이 있다고 해도 개념상 큰 차이가 있죠...

전자는 실행시간에 타입교체를 해야하지만 후자는 그럴 필요가 없다는거..

제 생각엔 여기서 static vs dynamic typing과 관련된 논의가 핵심일것 같은데

kldp에 이미 그런 논의가 있었다면 그쪽 자료를 참고해 보는게 좋지 않을까요?

velocica의 이미지

저는 모회사의 게임프로그래머 인데요...
템플릿....요놈 없음 게임 무지 느려져요....
템프릿을 활용해서 부동소수점계산시 느려지는 현상이 방지가 되기때문에,
또 쓸때없는 연산을 줄일수 이써서요...

게임개발시에는 그냥 코딩하고 나중에 릴리즈할때 템플릿으로 바꾸면 초당 프레임 2~3배 올라가고 그러던데요...^^;