C++ - 해당되는 글 5건
__declspec이라는 키워드는 Microsoft의 C++ 컴파일러가 알아먹는 속성들을 지정해줍니다. __declspec(attribute) 이런 식으로 쓸 수가 있는데, 대표적인 attribute로 dllimport, dllexport가 있습니다.

가끔씩 naked라는 attribute가 유용한 경우가 있습니다. 변수를 특별히 사용하지 않으면서 순수하게 코드만 따로 떼어내고 싶은 경우가 그렇습니다. 또, stack이 정해져 있지 않은 경우도 있을 수 있겠지요.

naked attribute를 사용한 함수와 아닌 함수의 코드가 어떻게 생성되는 지 비교해보겠습니다.

__declspec ( naked ) void NakedFunc(void)
{
    int i;
    __asm { mov eax, offset CodeBegin }
00413010  mov         eax,offset CodeBegin (413020h)
    __asm { mov ebx, offset CodeEnd }
00413015  mov         ebx,offset CodeEnd (41302Ah)
    __asm { mov ecx, __LOCAL_SIZE }
0041301A  mov         ecx,4      
CodeBegin:
    i = 1;
00413020  mov         dword ptr [i],1
    __asm { mov eax, dword ptr [esp] };
00413027  mov         eax,dword ptr [esp]
CodeEnd:
    printing();
0041302A  call        printing (41111Dh)
0041302F  int         3   
}

파란 색이 제가 작성한 코드이고, 까만 부분이 disassembly된 코드입니다. 위의 코드를 보시면 웃기게도 블럭 닫기 기호인 '}'에 해당하는 코드가 없습니다. 리턴하는 코드 조차 없다는 말입니다. 당연히 위와 같이 프로그래밍 했다면, 프로그램은 죽어버릴 것입니다. 리턴하지도 않거니와, 맨 마지막에 'int 3'이라는 명령 때문입니다. interrupt 발생시키는 명령인데, 3번은 디버거를 위한 것입니다.
물론, 당연하게도, 범용 레지스터를 저장한다던가, 이전 함수의 스택 포인터를 저장한다던가 하는 명령어들도 없습니다. 이런 것들을 함수의 prolog와 epilog라고 하는데, __declspec(naked)는 바로 prolog와 epilog를 컴파일러가 만들어주지 않습니다.

참고로, 'int 3' 명령은 모든 함수 밑에 있습니다. 디버깅 모드로 빌드해서 그런 지는 모르겠습니다만, 아무튼, 혹시나 프로그램 제어가 함수 밖으로 뛰쳐나갈 경우를 대비한 것인 듯 합니다.

void UnNakedFunc(void)
{
00413030  push        ebp 
00413031  mov         ebp,esp
00413033  push        ecx 
00413034  push        ebx 
00413035  mov         dword ptr [ebp-4],0CCCCCCCCh
    int i;
    __asm { mov eax, offset CodeBegin }
0041303C  mov         eax,offset CodeBegin (41304Ch)
    __asm { mov ebx, offset CodeEnd }
00413041  mov         ebx,offset CodeEnd (413056h)
    __asm { mov ecx, 0x00 }
00413046  mov         ecx,0      
CodeBegin:
    i = 1;
0041304C  mov         dword ptr [i],1
    __asm { mov eax, dword ptr [esp] };
00413053  mov         eax,dword ptr [esp]
CodeEnd:
    printing();
00413056  call        printing (41111Dh)
}
0041305B  pop         ebx 
0041305C  add         esp,4
0041305F  cmp         ebp,esp
00413061  call        @ILT+320(__RTC_CheckEsp) (411145h)
00413066  mov       esp,ebp
00413068  pop       ebp
00413069  ret

아.. 모양이 많이 다릅니다. 이 소스코드에서 __declspec(naked)와 또 다른 점은 __LOCAL_SIZE가 없다는 것인데, __LOCAL_SIZE는 컴파일러가 알아먹는 심볼로, 컴파일할 때, __declspec(naked)로 정의된 함수의 스택 크기를 집어넣어줍니다. 따라서 naked가 아닌 함수에는 사용할 수 없습니다. __LOCAL_SIZE의 용도는 위의 코드에서 진하게 표시한 부분을 직접 어셈블리로 작성해야 할 때 유용하게 사용하기 위한 것입니다. 함수의 지역 변수의 전체 크기를 사람이 직접 측정하는 것은 오류를 발생시킬 가능성이 크므로, 컴파일러가 대신 하여 넣어주는 것입니다.

아무튼! 일반적인 함수와는 달리, naked 함수는 좀 특별한 용도로 사용될 것 같죠? 보통 함수는 뭔가를 넣으면 안에서 처리해 그 결과를 돌려준다는 개념인데, naked 함수는 단지 코드의 집합 정도로 보는 것이 좋을 것 같습니다.

그런데!!!

놀라운 것은 위의 첫 번째 예제에서 __LOCAL_SIZE가 4가 아닐 수도 있다는 것입니다. 지역 변수가 정수형 하나이기 때문에 4라고 생각했는데, 192 + 12 이었습니다. 지역 변수가 없으면 192, 변수 하나에 8이 더 붙은 것입니다.

컴파일러 옵션으로 /ZI가 주어지면 이렇게 됩니다. 컴파일러 옵션에 'Debug Information Format' 항목에서 'Program Database for Edit & Continue' 옵션을 선택하는 것으로 설정할 수 있습니다. 이렇게 하면, 링킹할 때 /Gy 옵션이 따라 붙게 되는데, 함수를 어찌어찌 한다고 합니다.

설명서를 읽어도 잘 모르겠네요 ㅠㅠ 혹시 아시는 분 가르쳐주세요!

참, /ZI 옵션, __declspec(naked)는 모두 x86 코드에만 해당된다고 합니다.



Trackbacks  0 | Comments 
permalink 해보리
2009.01.12 09:31 댓글에 댓글수정/삭제
좋은 정보 감사합니다. 담아가도 되나요?
링크만 달아 갈께요.




왜 빈 껍데기 클래스 (empty class)의 크기는 하필 1byte일까?
즐겨 찾는 프로그래밍 관련 블로그 중 하나인 Alones World에 Alones 님께서 제 궁금증에 위와 같이 답해주셨습니다.

저것은 쓸 데 없을 것 같은 empty class도 사용자가 만들 것을 가정했기에 나온 방책이겠지요.

제가 지금 어떤 어플리케이션의 요구사항 및 테스트 케이스들을 도출하고 작성하고 있는데, 저와 제 상사의 의견이 불일치하여 그냥 상사의 의견대로 따르고 있습니다.
실제 개발자가 자신이 제작할 어플리케이션의 구체적 모습을 파악하게 하기 위한 요구사항을 어느 정도로 제시해 줄 것인가하는 문제는 역시 같은 개발자인 우리들로서 정의하기가 쉽지 않습니다. 불완전한 프로그램을 출시한 후에 수 많은 고객의 불만을 겪어 본 선배들은 단순히 문서를 많이 작성하는 것이 목적인가 할 정도로 구체적인 요구사항을 제시하고, 어플리케이션을 제작하는 수준으로 테스트 케이스 문서를 작성할 것을 요구합니다.
전문적인 프로그램이기 때문에 외주 개발자들을 믿지 못하는 것도 있지요. 하지만, 믿지 못하면 우리가 하면 되는데, 굳이 방대한 양의 문서를 만들 필요가 있나 합니다. 어떤 경우가 있으니 신경쓰라고 경우의 수만 알려주면 될 테고, 그들이 알지 못하는 것은 자세히 설명해주면 될 것인데, 책임 소재의 근거로 남겨두기 위해서라도 이렇게 자세하게 하는 것 같습니다.
결국 모든 것이 끝난 다음에 "아 우리가 이렇게 많은 문서를 작성했구나." 하고 뿌듯해 하는 정도로 밖에 효용이 없어 보입니다.

80년대 후반, 새로운 언어인 C++을 제작할 때
Bjarne Stroustrup도 우리가 하는 고민을 했을 테지요. 언어에서 발생할 수 있는 모든 경우의 수를 다 떠올리고 그에 대한 대응을 하였겠지요. 그는 어떻게 했을까요? 지금까지 C++을 사람들이 잘 사용하고 있는 것을 보면, 그의 대응은 실로 적절하고 효과적이었던 것 같습니다.
Trackbacks  0 | Comments 




이 글은 Dr. Dobb's Journal 의 'Object Registration and Validation - Making your C++ Ironclad' 라는 기사를 보고 썼습니다.

오늘 참 재미있는 디자인 패턴 하나를 보았습니다. 이 패턴을 제안한 Eric Gufford 라는 사람은 ANSI C++ 기술 위원회 소속이고, Pure C++ 이라는 책을 썼다고 합니다. 그만큼 C++ 기술에서는 권위자라는 이야기이겠지요.

이 패턴이 태어난 이유는 아래와 같은 상황 때문입니다.
AClass * pObj;
pObj->method();

위 코드의 문제가 무었인지는 C++을 대충 알고 있는 사람도 다 알 것입니다. 바로 객체가 생성되지 않았는데, 참조를 한다는 것이지요. 만일, 위의 두 문장 사이에 수 십 줄의 다른 코드가 있다면? 알아채기 쉽지 않을 것입니다.
컴파일러는 알 수 없습니다. 객체가 생성되는 것은 실행을 시켜봐야 아는데, 컴파일러가 알게 뭡니까?
실행해봐도 왜 문제인지 모를 수 있습니다. 디버거를 실행해서 pObj가 가지는 값이 무엇인지 확인해보고 기대한 값이 들어있지 않아서 코드를 거슬러 올라가다 보면 객체가 생성되지 않은 채 사용되어 발생한 문제라는 것을 알겠지요. 게다가 위의 코드는 실행될 때 에러를 발생시키지 않을 수도 있습니다. 가상 함수가 아닌 한 method() 함수는 어쨌든 실행될테고 이 함수가 건드리는 메모리 주소가 가용한 곳이라면 뭐 어쨌든 위의 코드가 실행될 때 에러가 발생하지는 않겠지요.
만일 이 코드가 중요하지 않은 기능에 들어있고, 평소에 잘 사용하지는 않는 기능이지만 언젠가는 쓸 그런 기능(예를 들어 웹브라우저 간의 북마크 내보내고 가져오기 같은 기능)이라면?

'Object Registration and Validation' 패턴은 생성되지 않은 객체의 메소드가 호출되는 것을 막아줍니다. 위 기사에서 언급된 전제는 다음과 같습니다.
  • 클래스의 모든 데이터 멤버는 private이고, 이들은 항상 (심지어 같은 클래스의 method들에서 조차도) Accessor(데이터 멤버의 값을 읽어들이는 method)와 Mutator(데이터 멤버의 값을 변경하는 method)에 의해서만 접근된다.
이런 전제 조건이 없으면 이 패턴은 의미가 없어집니다. 이 패턴이 적용되는 클래스는 다음과 같은 멤버를 가집니다.
class  Class : public Base {
private:
    static const long sm_clSerialNumber = 0x13F039AC;
    mutable long m_mlSerialNumber;
    void SetSanity() const throw();
    void ClearSanity() const throw();
    void SanityCheck(int,string const&) const throw(EXSanity_c);
 };
여기에 보면 두 개의 멤버 변수가 있습니다. 하나는 static으로 선언이 되어 있군요. 이 값은 클래스의 모든 인스턴스들이 가지는 고유한 값으로, 객체가 생성되면 객체의 생성자에서 이 값이 m_mlSerialNumber로 복사됩니다. 이 m_mlSerialNumber 값을 설정하고 제거하는 메소드가 SetSanity()와 ClearSanity()입니다. 여기서 중요한 것은 SanityCheck()라는 메소드입니다. 클래스의 생성자를 제외한 모든 메소드들은 항상 이 메소드를 가장 먼저 호출하여야 합니다. 이 메소드는 다음 두 가지 판단을 하고, 이것이 틀리면 위의 코드에서 명시한 것처럼 어떠한 '예외'를 발생시킵니다.
  1. 객체의 this 포인터가 0인가? 0이라면 객체가 생성되지 않은 것이라고 확언할 수 있지요.
  2. 객체의 this 포인터가 0이 아니라면 sm_clSerialNumber와 m_mlSerialNumber가 같은가. 즉, 생성자가 호출되었는지(따라서 SetSanity() 메소드가 호출되었는 지) 확인하는 것입니다.
    단순히 생성자가 호출되는 것을 확인할 것이라면 어째서 bool 데이터와 같은 것을 사용하지 않았을까요? 클래스 포인터 변수가 지역 변수이고, 여기에 0을 대입하지 않았고, 메모리 할당도 하지 않았다면 this 포인터는 포인터 변수가 가진 어떠한 값을 (가상) 메모리 주소로 여기게 될 것입니다. 우연히도 이 값이 0이면 좋겠지만, 그렇지 않다면...
    또, 객체의 메모리를 해제하기 위해 delete 연산자를 사용했는데, 0을 대입하지 않았다면...
    위의 두 가지 경우는 객체가 생성되었는 지 확인하기 위해 단순한 flag가 아닌 좀처럼 우연히 만나지 않을 특정한 값을 사용할 필요가 있다는 것을 보여줍니다.
만일 위의 두 가지 경우 중 하나 이상에 해당되면, SanityCheck() 메소드는 예외를 발생시킵니다. 그런데, 클래스의 어떤 메소드도 이 예외를 처리하지 않으므로, 객체를 사용하는 곳에서 예외를 처리해야 합니다. 그렇지 않으면 저자의 기사에 의하면 운영체제에 의해 예외 발생된 것이 기록된다고 하네요.

아무튼, 이러한 방법을 쓰면 분명 객체가 생성되었는지 확인하기가 매우 쉬워지고, 문제를 미연에 방지할 수 있습니다. 이 패턴은 이해하기 어려운 것도 아니고, 제가 이 패턴에 대해 설명드리려고 하는 것도 아니니 직접 기사를 참조하시면 되겠습니다. 기사에 소개된 예제만 보셔도 이해하실 수 있을 정도로 명확한 패턴입니다.

저는 이 패턴의 세 가지 문제를 지적하고 싶습니다.

우선 SanityCheck()라는 메소드가 초래하는 overhead입니다. 메소드가 호출될 때마다 매번 this 포인터가 0인지 검사하고 일련 번호를 검사합니다. 비록, 이 함수가 inline으로 사용된다고 하더라도, 이 모든 코드가 기계어로 번역될 때는 최소한 10개의 instruction은 될 것입니다. 게다가, if 문을 가지고 있습니다. 분기문은 프로그램 제어의 일관성을 해치기 때문에 성능 저하를 초래할 수 있는 부분이지요. 뭐 최근의 데스크탑용 프로세서는 이런 것을 효율적으로 처리할 수 있는 기능이 있겠지만..
물론 긴 메소드의 경우에는 이 overhead가 별 큰 영향이 없습니다. 하지만, 이 패턴은 accessor와 mutator에서 항상 SanityCheck()를 호출할 것을 요구하고 있습니다. SanityCheck()가 객체의 데이터 멤버를 사용하기 전에 그것을 사용할 수 있는지 확인하는 것이기 때문에 당연한 것이지요. 사실 이 패턴에서 같은 클래스의 메소드들 조차 데이터 멤버를 사용하려면 accessor와 mutator를 쓰라고 하기 때문에, 다른 메소드들에서는 SanityCheck() 메소드를 호출할 필요 조차 없습니다.
일반적으로 accessor는 설정할 값 하나만을 인자로 받고, 리턴값이 없는 inline 메소드로 정의됩니다. 메소드의 코드도 대입 연산 단 하나가 대부분일 것입니다. mutator는 인자 없이 멤버 변수 하나의 값을 리턴만 하는 메소드가 대부분입니다. 이런 초간단 코드에 두 세번의 비교와 분기문이 있는 코드가 들어가면 그 overhead는 두 세배나 된다고 볼 수 있습니다.
아무래도 저자는 이 overhead를 과소평가하고 있거나 제가 모르는 어떠한 테크닉을 염두에 두고 있는 것이 분명합니다.

두번째로는 가상함수가 사용될 때는 이 패턴이 무용지물이 됩니다. 가상 함수가 호출되기 위해서는 먼저 객체의 vtbl, 즉 가상함수 테이블을 참조해야 합니다. 그런데, 객체가 생성되지 않았다면 vtbl이 유효하지 않겠지요. 그러면 SanityCheck() 함수가 호출되기도 전에 메모리 참조 오류가 발생(UNIX 계열은 Sagmentation fault, Windows 계열은 Access violation)합니다. 운이 좋아 포인터 변수가 가리키는 (물론 메모리 할당이 되지 않은 임의의 숫자)주소에 어떤 함수의 시작접이 지정되어 있다면 오류가 발생하지 않겠지만, 프로그래머가 의도한 바는 아니겠지요.

마지막으로 이 패턴 자체가 가지고 있는 논리적 문제입니다. 만일 메모리 할당이 일어나지 않았는데도 포인터 변수, 즉 this 멤버의 값이 0이 아니고(초기화하지 않은 지역변수가 이렇게 되지요), this가 가진 값이 접근되어서는 안될 주소라면? SanityCheck() 메소드가 실행되고 this와 0을 비교할 때는 괜찮겠지만, 멤버 변수를 사용할 때, 즉 m_mlSerialNumber를 접근할 때 잘못된 메모리 접근 오류가 발생합니다. 이건 뭐 예외를 발생시킬 수도 없습니다.

저의 지식이 짧아서인지는 모르겠으나, 이 패턴이 유용하게 사용될 만한 곳이 분명히 있을 것이라 생각하지만 너무나 많은 문제를 가지고 있어서 효용이 있을지는 의문입니다. 혹시 누군가 저에게 이해시켜주실 분 계신가요??

-- 7월 2일 --
패턴의 저자가 제가 질문한 것에 대해 답변을 해주었네요.

Phillip, yes you're right that there are a few of instructions in each function, but they are simple assignment and cmp operations. THe only expensive operation is the actual throw.

Inlining, though, removes any need for stack push/pop, which are also sizeable operations. And any good optimizing compiler can reduce the footprint further, possibly down to 2 cmp ops and a short jmp (over the throw) into the heart of the function.

Re virtual functions, the vtbl resolution will already have been performed prior to the function being called - it is an operation required to find the correct entry point into the function in the first place. And inlining will have removed the function call overhead and instead embedded the SanityCheck function's instructions directly into the parent function (down to the 2 cmp and 1 jmp instruction referred to above). Thus, there is no problem with virtual functions that I can see.


함수가 Inline 되고, 컴파일러가 최적화를 잘 하면, SanityCheck() 메소드는 두 개의 비교 명령과 하나의 점프 명령으로 될 수 있을 것이라고 하는군요.
또한, vtbl 접근도 문제가 없을 것이라고 하는데.. 이것은 다시 질문해보렵니다.

Trackbacks  0 | Comments 




C++에는 IO Stream library와 STL이 표준 라이브러리로 있습니다.
둘은 모두 std라는 namespace를 사용합니다.
그 외에 C Standard Library도 사용할 수 있죠.
IO Stream은 입출력에 관련된 것이고,
STL은 자료구조와 알고리즘에 대한 것입니다.

C++을 주로 사용하는 프로그래머라면, 이제 Boost 라이브러리도 알아둘 필요가 있습니다.

STL을 사용해보신 분은 아시겠지만, MFC나 C standard library처럼, 클래스 정의나 함수 정의만 보고 "아 이것은 이렇게 사용하면 되겠구나.." 라고 하기 힘듭니다.

Boost는 한 발 더 나아갑니다. 완전 모르겠습니다.

우선 Boost 라이브러리에 대해 말씀드리지요.
Boost 라이브러리는 C++ 라이브러리입니다. 대부분이 STL처럼 템플릿 라이브러리입니다. 그리고, Boost 라이브러리는 여러 하위 라이브러리들의 집합입니다. 하위 라이브러리들은 모두 70개가 조금 안됩니다. 휴우~ 이것들을 다 공부할 필요는 없습니다. 필요한 것만 하면 되지요.
Boost 라이브러리는 뭐다라고 단정지어 말하기 힘듭니다. STL과 굳이 구분을 하자면, 저는 보통 이렇게 이야기합니다.

"STL은 알고리즘이고, Boost는 유틸리티야."

완전히 옳은 것은 아닙니다. 하지만, 이렇게 생각하는 것이 속 편합니다. 하지만, 이러한 경향이 매우 강하지요.
하위 라이브러리는 매우 다양합니다.
graph와 같이 알고리즘을 구현한 것도 있고, functional이나 lambda와 같이 functional programming을 지원하는 것도 있습니다. 그런가 하면, regex++과 같이 정규표현식을 파싱하는 것도 있고, iostreams와 같이 iostream 라이브러리를 확장한 것도 있습니다.
ref와 같이 크기가 매우 작은 (작은 헤더 파일 하나) 라이브러리도 있는가 하면, Spirit과 같이 엄청 큰 규모의 라이브러리도 있습니다.

한 마디로, Boost는 C++을 위한 종합 선물 세트!!!

그 중, 제가 요즘 공부했던 Spirit이라는 라이브러리는 문자열 파싱에 관심있는 분이라면 꼭 한 번 보시는 것도 좋습니다.
가장 유명한 Parser generator로는 Lex/Yacc과 그 후손들인 Flex/Bison과 같은 LALR(1) 파서 생성기가 있고, ANTLR과 같은 LL(k) 파서 생성기가 있습니다.
비록 성능은 이들에 미치지 못하지만, 매우 많은 장점을 지닌 파서 생성기가 바로 Spirit입니다.
장점만 나열해보지요.
  • 완전한 C++ 코드이다. 따라서 C++에서 사용하는 디버깅 등이 모두 사용 가능하다.
  • Debugging과 Exception(Error) handling을 위한 많은 기능이 제공된다.
  • C++ 컴파일러 외에 다른 툴이 필요하지 않다.
  • <중요> Symbol table과 Parser Tree, Abstract Syntax Tree를 알아서 만들어주거나 매우 만들기 쉽다.
  • 동적 파서를 쓸 수 있다. (이거 참 유용할 듯합니다.)
제가 네 번에 걸쳐 세미나를 했을 때, Lex/Yacc에 비해 좋은 점을 모르겠다고 합니다. 그 분은 Lex/Yacc에 익숙하니까 그렇죠. Yacc에 symantic action을 넣으려면, C 코드로 난잡하게 집어넣고, 순서도 맞춰야 하고... 좀 복잡하죠. 그런데, Spirit은 symantic action이 들어갈 곳이 매우 직관적입니다.
그리고, 구문 분석기와 어휘 분석기가 따로 존재하지 않습니다. 그냥 알아서 합니다. 하하하

Spirit을 이해하기 위해서는 C++ 템플릿을 가지고 똥도 닦을 수 있어야 합니다. Spirit은 템플릿 라이브러리입니다. 게다가, Expression Template이라는 것을 사용합니다. (이전 글에서 잠깐 언급했는데, 이해하기 상당히 난해합니다. -_-;) STL에서도 나오는 Function object 혹은 Functor라는 것은 땅바닥에 눌러붙은 껌처럼 생각할 수 있어야 합니다.

그러한 과정을 거친 끝에, 겨우 온라인 문서를 다 봤습니다. 아.. 대충 이해는 가는데, 써먹으려니 또 막막하네요. 호호 그래도 많이많이 써먹어야죠.

아직까지는 C++과 같은 복잡한 문법 컴파일러는 못만든다고 합니다. C 파서는 Spirit으로 누가 만들어놨더군요. 저는 C++코드를 파싱하여 Visual Studio 등과 같은 툴에 있는 'Class view'와 같은 것을 만드려고 합니다. 어짜피 타입 이름과, 식별자들만 파악할 거니까 그리 어렵지 않게 할 수 있겠죠?

C++을 쓰시는 분들.. Boost 라이브러리 한 번 거들떠 보자.
Trackbacks  1 | Comments 




Expression Template은 Todd Veldhuizen이라는 사람이 1995년에 발표한 논문입니다. 논문 본문은 아래의 링크에 있습니다.

http://osl.iu.edu/~tveldhui/papers/Expression-Templates/exprtmpl.html

Expression Template이라는 것은 참 재밌습니다.

int foo(int a);

라는 함수가 있다고 하죠. 이 함수는 정수를 입력받아 정수를 리턴합니다.

int i = 20;

이라고 i라는 변수를 정의했습니다.

foo(i * 30 + i / 2);

와 같이 호출했다면 어떤 일이 벌어질까요? 프로그램이 실행 중일 때, i가 20인 걸 알고, 실제로는 다음과 같은 호출이 일어납니다.

foo(610);

그렇죠? 이를 early evaluation이라고 합니다. 미리 계산되어 인자로 들어간다는 의미지요.
그런데, Expression template은 lazy evaluation입니다. 수식이 일단 인자로 들어가고, 함수 내부에서 계산이 이루어집니다. 진짜 내부 구현을 들여다보면 좀 다르게 구현되어 있지만, 개념 상으로는 그렇습니다. 자세한 구현은 논문을 보시면 될 테고,

template<typename T>
int bar(T a);

라는 함수가 있고,

class Ca;
Ca x;

라는 클래스와 그것의 인스턴스가 있습니다. 다소 놀라운 일이 벌어집니다.

bar(x * 30 + x / 2);

이렇게 호출 할 수 있습니다. 뭐 이렇게 얘기할 수 있겠죠.
*와 +와 /가 Ca라는 클래스에 대해 operator overloading 되어있으면 되고, bar()라는 함수는 결국 Ca 타입의 reference나 value를 입력받지 않겠느냐
네, 맞습니다. 그렇게 됩니다. (하지만, 최종으로 넘어가는 클래스가 Ca가 아니라는 거~~) 여기까지는 이상할 게 없죠? 그런데 문제는 x가 무엇이냐는 겁니다. 그냥 단순히 객체를 넘기고자 했다면, 저렇게 요상한 식을 써서 넘길 리가 없죠. 저건 누가 봐도 수식입니다. 뭔가 계산하고 싶은 것이고, x는 (객체로 구현이 되어는 있지만) 변수입니다.

여기서는 argument placeholder라고 해서, 나중에 그 값이 결정됩니다. 눈치 빠른 사람들은 뭐가 다른 것인지 아시겠죠?

네, foo()에서 i는 입력으로 들어갈 때 이미 값이 20이라고 결정되어 있지만, bar()의 x는 값이 결정되어 있지 않습니다. 그럼, 언제 그 값을 알 수 있을까요?

여기서 functor 혹은 function object라는 것이 등장합니다. 이것은 STL을 공부하신 분은 아실 것입니다. 객체를 함수처럼 사용하는 겁니다. 즉, 클래스가 operator()() 함수를 가지고 있는 것이지요. 아니면, 미리 약속된 이름의 멤버 함수가 있을 것입니다. bar() 함수에서, 파라미터로 넘어간 객체의 특정 메쏘드를 호출할 때, 그 값이 결정됩니다. 즉,

x는 'x * 20 + x / 2'가 만들어내는 functor의 parameter입니다.

여기서 또 새로운 개념, Lambda Expression이 들어갑니다. x가 바로 Lambda... 음..

그리고, *, +, / 즉, operator*(), operator+(), operator/()는 모두 higher order function  혹은 functional이라고 부릅니다. 함수를 입력받아, 함수를 리턴하니까요...

오늘 세미나 시간에 이걸 설명하려고 했습니다... 어제부터...

그런데... 역시나.. 설명이 잘 안됩니다..

저도 공부하는데 무지 오래걸리긴 했는데, 전달도 잘 안되네요.. 아직 빠삭하게 알지 못해서리.. STL도 잘 모르는데요 뭐..

왜 이런 것들을 설명하냐구요?

Boost의 Spirit이라는 라이브러리를 설명하는 중이었는데, 저것들을 알아야해요~~ -_-;


Trackbacks  0 | Comments 




풀리비’s Blog is powered by Daum & Tattertools.com