원문 : C++ Modifier Types


주의 : 번역이 개판이므로 원문을 참조하세요. 

주의 : 허락받고 번역한 것이 아니기 때문에 언제든 내려갈 수 있습니다.

주의 : 가독성을 높이기 위해 잘 알려진 용어나 발음이 비슷한 용어는 한글 표기합니다.


C++ Modifier Types


C++ 은 char, int, double data type 앞에 modifier 를 가질 수 있도록 허용한다. Modifier 는 base type 의 의미를 수정하는데 사용되며, 그래서 다양한 상황에 대한 요구를 더 정확하게 만족시킨다.


그 data type modifier 의 리스트는 아래와 같다 :

  • signed
  • unsigned
  • long
  • short


signed, unsigned, long, short 는 integer base type 들에 적용될 수 있다. 부가적으로 signed 와 unsigned 는 char 에 적용될 수 있으며, long 은 double 에 적용될 수 있다.


signed 와 unsigned 는 long 이나 short 앞에 사용될 수도 있다. 예를 들면 unsigned long int 이다.


C++ 은 unsigned, short, long 의 integer 를 선언하기 위한 단축 표기를 허용한다. 당신은 int 없이 단순히 unsigned, short, long 만을 사용할 수 있다. int 는 내포되어 있다. 예를 들어 다음의 두 문장은 둘 다 unsigned integer 변수를 선언한다.



signed 와 unsigned 가 C++ 에 의해 해석되는 방식의 차이를 이해하기 위해서는, 다음과 같은 짧은 프로그램을 실행해 보면 된다 :



프로그램이 실행되면, 출력은 다음과 같다.


-15536 50000


위의 결과는 50,000 을 short integer 로서 표현하는 bit 패턴이 short 에 의해 -15,536 으로 해석되기 때문이다.


Type Qualifier in C++


Type qualifier 는 변수 앞에서 부가적인 정보를 제공한다.


 Qualifier   Meaning 
 const 

 const type 의 개체는 프로그램 실행 동안에 변경될 수 없다.

 volatile

 volatile 은 컴파일러에게 변수의 값이 프로그램에 의해서 명시적으로 지정되지 않은 방식으로 변경될 수 있음을 통보한다.

 restrict

 restrict 에 의한 pointer qualifier 는 초기에 단지 그것이 가리키는 개체에 의해서만 접근 가능하다는 것을 의미한다. C99 에서만  restrict 라 불리는 새로운 유형의 qualifiler 가 추가되었다.




volatile 과 restrict 는 정말 잘 안 쓰이는 keyword 이다. 자세한 내용은 다음 링크들을 참조하기 바란다.


C/C++ volatile 키워드.

C/C++ volatile 에 대한 오해.

[ C/C++ ] volatile 선언의 의미에 대한 정리.

restrict, WIKIPEDIA.


주의 : 공부하면서 정리한 내용이므로 오류가 있을 수 있습니다.


실습 과제 2-6* :


처음에는 Boost 형식 특질 라이브러리가 제공하는 형식 산술이 완벽해 보이겠지만, 사실은 그렇지 않다. 그 라이브러리의 수단들이 포괄하지 않은 형식 범주들과 관계들, 변환들이 적어도 몇 개는 존재한다. 예를 들어, 그 라이브러리는 부호 있는 정수 형식에 대한 부호 없는 버전의 정수 형식을 얻을 수 있는 수단을 제공하지 않는다. 그런 빠진 부분들을 최대한 많이 찾아 볼 것. 각 특질 범주마다 적어도 하나는 있으며, 전체적으로 적어도 11 개는 있다. 빠진 특질들 각각의 인터페이스를 설계하고, 그런 특질이 필요함을 보여 주는 용례도 제시하라.




풀이 :


일단 이 문제를 풀기 위해서는 "형식 산술" 이 의미하는 바에 대해서 이해할 필요가 있다. 책에서는 "형식 산술" 을 다음과 같이 정의하고 있다.


[ 표 2.5 ] 에 나와 있는 메타 함수들은 근본적인 형식 계산들을 수행하는 메타 함수들이다. 지금까지 살펴 본 다른 형식 특질 메타 함수들과는 달리, 이들은 부울 값이 아니라 형식을 계산 결과로 낸다. 이들을 "형식 산술" 의 연산자들이라고 생각하면 될 것이다.


- C++ Template Metaprogramming, 2.7 메타함수의 정의, 48 페이지.


형식 산술을 위한 메타 함수의 리스트는 다음과 같다.



위의 형식 산술 연산자들은 modifier 나 qualifier 를 붙였다가 떼는 역할을 수행하고 있다. 일단 modifier 와 qualifier 의 개념에 대해서 알고 있어야 어떤 것이 가능하고 어떤 것이 불가능한지 판단할 수 있을 것이다. 다음 링크를 참조하라 : [ 번역 ]C++ Modifier Types.


위의 boost 메타 함수들은 modifier 와 관련한 형식 산술 연산자를 제공하고 있지 않다는 것을 알 수 있다.


1. add_unsigned< T >


unsigned modifier 를 추가한다.


용례 : 



이것을 직접 구현하려고 했을 때 일반적인 template 구현으로는 구현이 불가했다. 다음과 같은 template 은 에러를 발생시킨다.



const  같은 qualifier 는 "const T" 와 같은 부분 특수화가 가능했지만, 일반적인 modifier 는 그것이 불가능했다. 아마도 modifier 들은 정수형의 기본형에 대해서만 성립하는 것이기 때문에 그런 것이 아닌가 싶다. 예를 들어 unsigned std::vector 같은 표현은 성립하지 않는다. 혹은 [ 번역 ]C++ Modifier Types 에서도 언급하고 있듯이 unsigned 자체가 단축표기로 하나의 형을 나타내기 때문이 아닌가 싶다. 이 경우에는 add_unsigned< unsigned int + T > 의 형태가 되는게 아닌가 싶다.


어쨌든 이를 구현하기 위해서는 다음과 같이 모든 기본형에 대해서 부분 특수화를 하는 수밖에 없다. 아래는 int 에 대해서 부분 특수화를 한 예이다.



2. remove_unsigned< T >


unsigned modifier 를 제거한다.


용례 :



3. add_signed< T >


signed modifier 를 추가한다.


4. remove_signed< T >


singed modifier 를 제거한다.


5. add_long< T >


long modifier 를 추가한다.


6. remove_long< T >


long modifier 를 제거한다.


7. add_short< T >


short modifier 를 추가한다.


8. remove_short< T >


short modifier 를 제거한다.

















주의 : 공부하면서 정리한 것이기 때문에 오류가 있을 수 있습니다.


실습 과제 4-5 :


다음 함수 템플릿은 std::find 에 대한 "컨테이너 기반" ( 반복자 기반이 아니라 ) 인터페이스를 제공하기 위한 것이다.



현재 코드에서, container_find 는 const 컨테이너들에 대해서는 동작하지 않는다. 코드는 std::find 가 돌려 주는 const_iterator 를 Container::iterator 로 변환하려 하는데, 임의의 컨테이너 형식 x 에 대해 Container 는 const x 가 되기 때문에 컴파일이 실패한다. 이 문제를, container_find 의 반환 형식을 계산하는 작은 메타프로그램을 작성하여 해결하라.




풀이 :


이 코드를 보고 "Container& 인데 왠 const 컨테이너가 들어 온다는 거지?" 라는 의문을 가질 수 있다. 하지만 저건 그냥 type 이 아니라 template 매개 변수라는 것에 주목해야 한다. Container 자체에 예를 들면 const vector< int > 로 선언된 것이 들어 올 수 있다.


일단 에러가 나는 상황을 재현해 보자. 



이 문제를 해결하는 것은 간단하다. 다음과 같은 메타 함수를 하나 만들어서 반환값이 들어갈 부분에 넣어 주면 된다. 의미를 이해하는 것은 크게 어렵지 않을 것이다.



여기에서 특이한 것은 mpl::identity 를 사용한다는 것이다. mpl::eval_if 는 말 그대로 메타 함수를 평가하는 것이기 때문에 mpl::identity 를 사용하지 않으면 그냥 return 값이 make_iterator< Container > 가 되어 버린다. make_iterator 를 메타 함수로서 실행해 주기 위해서는 mpl::identity 를 상속한 형태가 되어야 하며, 최종적으로는 make_iterator< Container >::type 을 실행함으로써  Container::const_iterator 나 Container::iterator 가 반환된다. make_iterator 가 결국은 mpl::identity 를 상속하게 되기 때문에 mpl::identity::type 을 실행한 것과 동일한 효과를 내는 것이다.


이해를 돕기 위해서 mpl::eval_if 가 실행되고 나서의 결과를 단순하게 표현한다면 다음과 같다.



전체 코드는 다음과 같다.





다른 사람의 구현도 살펴 봤는데 필자와 다르지 않았다.


http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?CPPTM_Answers_-_Exercise_4-5


원문 : C++ Operator Overloading Tutorial, Function Call Operator ()


주의 : 번역이 개판이므로 이상하면 원문을 참조하세요.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.

주의 : 가독성을 높이기 위해서 잘 알려진 용어나 발음이 비슷한 용어는 한글로 표기합니다.


Function Call Operator ()


C++ 에서 함수 호출 연산자는 () 연산자이다. 이것은 한 가지 중요한 차이점을 제외하고는 이전에 설명한 subscript 연산자( 역주 : [] )와 유사하게 중복정의된다( overloaded ). subscript 연산자는 단지 하나의 인자만을 받는 반면에, 함수 호출 연산자는 다양한 형식의 여러 인자들을 받을 수 있다. 이러한 특별한 사실은 우리에게 매우 중요하다.


예를 들어 우리가 2 차원 배열 클래스를 가지고 있다면 우리는 배열의 특정 cell 을 참조하기 위해서 [] 를 중복정의할 수 없다. 왜냐하면 [] 는 인자로 하나의 값만을 취하기 때문이다. 그러나 우리는 () 연산자를 중복정의할 수 있다. 왜냐하면 그것은 다중의 인자들을 취할 수 있으며, 즉 우리는 인자로 row 와 column 을 지정할 수 있다. 사실 그 함수 호출 연산자는 단지 다양한 형식의 가변 개수의 인자들을 취할 수 있는 중복정의된 연산자일 뿐이다. 이는 우리가 다양한 상황에서 그것을 사용( 혹은 잘 못 사용)할 수 있게 해 준다.


우리의 예제를 위해, 우리는 () 연산자가 [] 연산자와 동일한 일을 수행하도록 중복정의하고 이전 예제를 빌드할 것이다. 공교롭게도 어떤 언어들( BASIC 이나 FORTRAN )은 () 를 배열 subscripting 을 위해 사용한다. 그래서 사람들은 그러한 방식으로 사용되는 () 을 보는데 익숙하다. 우리는 클래스에 다른 멤버 함수를 추가함으로써 작업을 시작한다.



이 함수 구현은 이전 페이지의 것과 매우 유사하다.



이전처럼 우리는 이 개체를 다음과 같이 검사할 수 있다 :



이 코드를 실행하면 원하는 결과를 산출한다.


이전 페이지( subscript operator )에서 보았듯이, 항상 참조를 반환할 필요는 없다. 예를 들어 당신이 개체를 읽기 전용으로 만들기 원한다면, int& 참조 대신에 const int 를 반환하도록 만들 수 있다.


Excercises


1. 우리의 중복 정의된 연산자는 배열 범위 내에 index 가 있는지 검사하지 않는다.  필요하면 예외를 던지도록 코드를 추가하라. 예외를 던지는 대신에 필요하면 배열 크기를 변경할 수 있는 다른 버전도 만들어 보라.


2. 컴파일러에 의해 생성되는 기본 복사 생성자는 문제가 있다. 그것은 개체의 인스턴스로부터 다른 인스턴스로 데이터를 복사하는 대신에 포인터를 복사한다. 두 개의 포인터가 같은 메모리를 가지고 있기 때문에 하나가 파괴가 될 때 문제가 발생한다. 다른 인스턴스의 포인터는 해제된 메모리를 가리키게 된다. 해결책은 자신만의 복사 생성자를 작성해서 적절하게 데이터의 복사본을 만드는 것이다. 적절하게 데이터를 다루기 위한 복사 생성자를 작성하라.


3. 2 차원 배열을 다루기 위해 클래스를 재작성하라. 다음 코드는 당신이 작업을 시작하는데 도움을 줄 것이다.



아래 코드로 검증하라.



출처 : MSDN, explicit( C++ )


주의 : 번역이 개판이므로 원문을 참조하세요.

주의 : 가독성을 높이기 위해, 잘 알려진 용어나 발음이 비슷한 용어는 한글로 표기합니다.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.


explicit ( C++ )


하나의 매개변수만을 가지는 C++ 생성자는 자동적으로 묵시적( implicit ) 형변환을 수행한다. 예를 들어, 생성자가 문자열 포인터를 기대하고 있을 때 당신이 int 를 넘겨준다면, 컴파일러는 int 를 문자열 포인터로 변환해야만 한다는 코드를 추가할 것이다. 그러나 당신이 이러한 자동적인 행위를 항상 원하는 것은 아닐 것이다.


당신은 생성자 선언에 explicit 를 추가하여 묵시적인 변환을 방지할 수 있다. 이는 그 코드를 올바른 형식의 매개변수를 사용하거나 올바른 형식으로 매개변수를 변환하는 것을 강제한다. 즉 그 변환이 명시적으로( visibly ) 코드에 표현되어 있지 않다면, 에러가 발생할 것이다.


explicit 키워드는 명시적으로 개체를 생성하기 위해서 in-class 생성자 선언에만 적용될 수 있다.


Example


다음 프로그램을 컴파일하는 것은 에러를 산출한다. 이 코드는 묵시적 형변환을 수행하려고 하지만, explicit 키워드의 사용이 그것을 방지한다. 이 에러를 해결하기 위해서는 explicit 키워드를 제거하고 g 의 코드를 적용하라.



원문 : http://www.cprogramming.com/tutorial/template_specialization.html

주의 : 번역이 개판이므로 원문을 참조하세요.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.

정보 : 가장 아래에 있는 관련 기사들도 같이 보시면 좋을 것 같습니다.


Template Specialization and Partial Template Specialization


Template Specialization


Template 을 가지고 작업을 하는 많은 경우에, 당신은 모든 가능한 data type 을 위한 범용적인 버전의 template 을 작성할 것이며, 그것을 그대로 둘 것이다 -- 모든 vector 는 아마도 정확히 같은 방식으로 구현될 것이다. Template 특수화라는 아이디어는 기본 template 구현을 덮어 써( overriding ) 특정 type 을 다른 방식으로 다루는 것을 의미한다.


예를 들어 대부분의 vector 들이 주어진 type 에 대해 배열로서 구현될 것이지만, 당신은 약간의 메모리를 절약하기로 결심하고, bool 의 vector 들을 vector 내의 하나의 요소( entry )가 각 bit 와 연관되는 integer 의 vector 로서 구현할 수 있다. 그래서 당신은 다음과 같은 두 개의 개별적인 vector class 들을 가질 수 있다. 첫 번째 class 는 다음과 같다.



그러나 그것이 bool 일 때 당신은 실제로는 이렇게 하고 싶지 않을 것이다. 왜냐하면 대부분의 system 들이 1 bit 가 필요함에도 불구하고 boolean type 을 위해 16 bit 나 32 bit 를 사용하기 때문이다. 그러므로 우리는 integer 배열로 data 를 표현함으로써 boolean vector 의 모양을 약간 다르게 만들어야 한다. 그것의 bit 들은 우리가 수동으로 조작하게 된다. ( bit 를 직접적으로 조작하는 것과 관련해 더 알고 싶다면, bitwise operators and bit manipulation in C and C++ 을 참조하라. )


이렇게 하기 위해 우리가 template 과 유사한 것을 가지고 작업하지만 이번에는 template 의 매개변수( parameter ) 가 비어있을 것이라는 것을 지정할 필요가 있다 : 



그리고 그 class 의 이름 뒤에는 특화된 type 을 넣는다 : class className< type >. 이 경우에는 그 template 이 다음과 같다 :



만약 특화된 버전의 vector class 가 범용 vector class 보다 더 다양한 interface( public method 집합 )를 가지고 있다면, 그것은 완벽하게 합리적일 것이다 -- 비록 둘 다 vector template 이지만, 그것들은 어떠한 interface 나 code 도 공유하지 않는다.


이 경우에 특화를 하는 가장 중요한 이유는 더 공간을 효율적으로 이용하는 구현을 허용하는 것이었음을 짚고 넘어 갈 필요가 있다. 그러나 당신은 이것이 쓸모가 있는지에 대해 생각해볼 수 있다 -- 예를 들어, 당신이 어떤 type 에 의존하는 templated class 에 부가적인 method 를 추가하고 싶지만 다른 type 에 대해서는 그렇게 하고 싶지 않은 경우이다. 예를 들어, 당신이 상속을 더 좋아하기는 하지만, 당신은 각 요소의 non-integer 부분을 반환하는 method 를 가지는 double 의 vector 를 가지고 있을 수 있다. 그러한 부가적인 기능을 가지지 않는 double 의 vector 가 존재하지 않도록 막을 특별한 이유는 없다. 그러나 만약 당신이 그것이 쟁점이라고 느끼고 그것을 막고자 한다면, 당신은 template 특수화를 사용해서 그것을 수행할 수 있다.


어떤 template 을 특수화하기를 원하게 되는 다른 경우는 당신이 template 을 저장하고자 하는 class 의 collection 에 구현되지 않은 어떤 동작( behavior )에 의존하는 template type 을 가지고 있을 경우이다. 예를 들어 당신이 > 연산자가 정의되어 있을 것을 요구하는 templated sortedVector 를 가지고 있고, 누군가에 의해서 작성된 class 집합은 overload 된 연산자를 포함하고 있지는 않지만 비교를 위한 함수를 가지고 있다고 한다면, 당신은 template 을 특수화해서 그 class 들을 개별적으로 다룰 수 있다.


Template Partial Specialization


부분 template 특수화는 위에서 기술한 전체 특수화와 비슷한 동기를 가진다. 그러나 이번에는 하나의 특정 type 을 위해서 class 를 구현하는 것이 아니라, 여전히 다른 매개변수화( parameterization )를 허용하는 template 을 작성하는 데서 끝날 것이다. 즉 당신은 하나의 기능( feature )을 특수화하는 template 을 작성하지만, 여전히 그 class 의 user 는 template 의 일부로서 다른 기능들을 선택할 수 있도록 한다. 예제를 통해 이를 좀 더 확실하게 하자.


Vector 의 개념을 확장하는 아이디어로 돌아가 보자. 그러면 우리는 sortedVector 를 가지고 있다. 이것이 어떻게 보일 것인지 생각해 보자; 우리는 비교를 위한 방법을 필요로 할 것이다. 좋다; 만약 연산자가 정의되어 있다면 그냥 > 를 사용하고, 그렇지 않다면 특수화를 한다. 그런데 이제 우리가 정렬된 vector 안에 object 에 대한 pointer 를 가지고 있기를 원한다고 가정해 보자. 우리는 pointer 의 값으로 그것들을 정렬할 수 있을 것이다. 단지 표준 > 비교를 수행하기만 하면 된다( 우리는 오름차순으로 정렬되는 vector 를 가지게 될 것이다 ).



이제 위의 반복문에서 우리는 T type 의 요소들 사이에서 직접 비교를 수행하고 있다는 것을 알게 될 것이다. 대부분의 경우에는  괜찮다. 그러나 아마 pointer 주소가 아니라 실제 object type 상에서 정렬되는 것이 더 말이 될 것이다. 그렇게 하기 위해서 당신은 code 를 다음과 같은 line 을 가지도록 작성해야할 필요가 있다 :



물론 그것은 non-pointer type 에 대해서는 작동하지 않는다. 여기에서 우리가 하고 싶은 것은 그 type 이 pointer 인지 non-pointer 인지 여부에 의존하는 부분 특수화이다( 당신은 pointer 의 multiple level 을 가지고 싶을 수도 있다. 하지만 그냥 단순하게 하자 ).


모든 pointer type 을 다루는 부분적으로 특수화된 template 을 선언하기 위해서, 우리는 이 class 선언에 다음을 추가할 것이다 :



여기에서 주목해야 할 두 개의 syntax point 가 있다. 첫 번째는 우리 template 매개변수 리스트가 여전히 매개변수의 이름을 T 라고 짓고 있지만, 그 선언은 class 의 이름 뒤에 T* 로 붙는다; 이는 컴파일러에게 모든 type 의 pointer 에 대해 더 범용적인 template 대신 이 template 을 연결해 달라고 요청하는 것이다. 두 번째는 T 가 이제 pointer 로 접근하는 type 이라는 것이다; 그것 자체는 pointer 가 아니다. 예를 들어 당신이 sortedVector< int* > 를 선언할 때, T 는 int type 을 참조할 것이다! 이는 당신이 그것을 별표( * )가 붙은 type 이 있을 때 그것을 매칭해 주는 형식이라고 생각하면 이해가 쉽다. 이는 당신이 구현을 할 때 좀 더 주의를 해야 한다는 것을 의미한다 : vec_data 는 T** 이다. 왜냐하면 우리는 pointer 로 구성된 동적 크기의 배열을 필요로 하기 때문이다.


당신은 sortedVector type 이 실제로 이렇게 동작기를 원하는지 궁금해 할 수 있다 -- 결국 만약 그것들을 pointer 의 배열로 삽입한다면, 당신은 그것들이 pointer type 에 의해 정렬될 것을 기대할 것이다. 그러나 이렇게 하는 것에는 실질적인 이유가 존재한다 : 당신이 object 에 대한 배열을 위해 memory 를 할당할 때, 반드시 각 object 를 생성하기 위해 기본 생성자가 반드시 호출되어야만 한다. 만약 기본 생성자가 존재하지 않는다면( 예를 들어 모든 object 가 생성되기 위해서는 어떤 data 를 필요로 한다면 ), you're stuck needing a list of pointers to objects, 그러나 당신은 아마도 그것들이 실제 object 들 자신이 정렬되는 것처럼 정렬되기를 원할 것이다.


그건 그렇고 당신은 template 매개변수에 부분 특수화를 수행할 수도 있다 -- 예를 들어 만약 당신이 fixedVector type 을 가지고 있는데 그것이 ( 동적 memory 할당 비용을 피하기 위해서 )  class 의 사용자가 저장될 type 과 vector 의 길이를 모두 지정할 수 있도록 허용한다고 하면, 다음과 같은 코드를 볼 수 있게 될 것이다 :



그리고 나서 당신은 다음과 같은 구문으로 boolean 들을 부분적으로 특수화할 수 있을 것이다 :



T 가 더 이상 template 매개변수가 아니기 때문에 그것은 template 매개변수 리스트 밖에 있으며, length 만 남게 된다는 데 주의하라. 그리고 그 length 는 ( 범용 template 선언을 할 때는 이름 뒤에 아무것도 지정하지 않는 것과는 다르게 ) 이제 fixedVector 의 이름으로 나타난다. ( 그건 그렇고 template 매개변수가 non-type 이라는 것을 보고 놀라지는 마라 : 그것은 완벽하게 유효하며, unsigned 와 같은 integer type 인 template 인자들을 가지기 위해 유용하게 쓰이기도 한다. )


최종 구현 세부사항이 부분 특수화까지 왔다 : 컴파일러는 완벽하게 범용적인 type 들에 대한 조합, 어떤 부분 특수화, 그리고 완전한 특수화가 존재할 때 어떤 특수화를 선택할까? 일반적인 경험 법칙( rules of thumb )은 컴파일러가 가장 명확한( specific ) template 특슈화를 선택할 것이라고 이야기한다 -- 가장 명확한 template 특수화는 다른 template 선언들에 의해서 받아들여질 수 있는 template 인자들을 가지고 있지만, 같은 이름을 가진 다른 template 들이 받아들일 수 있는 인자를 모두 받아들일 수는 없는 template 을 의미한다.


예를 들어 만약 sortedVector< int* > 가 memory 위치에 의해 정렬되기를 원한다고 할 때, 당신은 sortedVector 에 대한 전체 특수화를 생성할 것이다. 그리고 sortedVector< int* > 를 선언하면 컴파일러는 pointer 에 대해 덜 명확한 부분 최적화를 선택할 것이다. int* 는 전체 특수화와만 일치하지만 double* 과 같은 다른 pointer type 와는 일치하지 않기 때문에 그것이 가장 근접하게 특수화된 것이다. 반면에 int* 는 명백히 다른 template 들이 받아들이는 매개변수가 될 수 있다.




Related articles.

C++ Templates Quiz : 당신의 template 지식을 테스트해 보세요.

Templated classes in C++ : templated class 에 대한 배경 정보.

Templated functions : 함수 template 에 대해 더 많이 배워 보세요.


'Programming > C++' 카테고리의 다른 글

[ 번역 ] Universal CRT 소개  (0) 2015.12.28
[ 번역 ] C++ Modifier Types  (0) 2013.01.02
[ 번역 ] Function Call Operator ()  (0) 2012.12.24
[ 번역 ] explicit( C++ )  (0) 2012.12.24
[ 번역 ] C++ Metadata - Part 1, Singletons and Lookup  (0) 2012.10.31

C++ Metadata - Part 1, Singletons and Lookup


원문 : http://seanmiddleditch.com/journal/2012/01/c-metadata-part-i-singletons-and-lookup/

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.

주의 : 번역이 개판이므로 이상하면 원문을 참조하십시오.


Class metadata 는 C++ application 이 C#, Python, Java 와 같은 다른 고수준 언어에서 이용할 수 있는 runtime reflection 과 introspection 의 꽤 많은 부분을 가질 수 있도록 해 준다. C++ 은 어떠한 실제 metadata system 도 가지고 있지는 않지만( dynamic_cast<> 을 다루는 데 급급한, 거의 사용되지 않는 typeinfo/RTTI system 은 배제하고 ), 그 작업을 수행하고 만족할 만한 성능을 내고 사용하기 쉬운 시스템을 빌드할 수 있다. 이것은 엄청나게 큰 주제이기 때문에 이 기사를 몇 개의 시리즈로 분할할 것이다. 오늘은 metadata system 을 위해 가장 유용한 singleton pattern 과 몇 개의 입문 주제 및 대안들에 대해서 이야기할 생각이다.


Why Metadata


여러 번 들어 왔던 질문은 "왜 아무도 이것과 비슷한 system 을 원하지 않는거죠?", "정말 느리거나, memory 를 많이 차지하거나, 덜 유연한 해결책들보다 계산 비용이 더 많이 들지 않나요?" 이다. 나는 보통 이러한 질문들을 오래된 "game 을 위해서 가상 함수는 너무 느리지 않나요?" 나 다소 오래된 "component 나 data-driven 설계는 유연성을 증대하기에는 비효율적이지 않나요?" 라는 질문과 동일하다고 생각한다.


Metadata 를 사용하는 이유는 단순히 다음과 같다 : game engine 에서 runtime data-driven system 을 위한 metadata 는 부가적인 유지 비용이나 버그를 최소화하는 것을 가능하게 한다. Metadata system 은 부가적인 코드를 거의 추가하지 않고 단ㅅ누한 factory 들을 생성하는 것을 가능하게 한다. 그것들은 game object 들을 위한 serialization 을 사소하게 만든다. 그것들은 game editor window 나 debug toolbar 에서 편집중인 property 들을 매우 단순해지도록 만들어 준다. Metadata 는 강화된 debugging output 을 지원한다. Metadata 는 개체를 문서화하는 것을 돕는다. Metadata 는 script binding 을 위해서도 사용될 수 있다.


This all stems from the basic notion of what metadata is in programming language. Metadata 는 단지 base type system 에 부가적인 정보를 추가한 것 뿐이다. 매우 단순한 예제로 class 의 이름을 들 수 있다. C++ 에서는 runtime 에 보통 다음과 같은 질문을 던질 방법이 없다, "내 Foo* 변수에 의해 참조되는 class instance 의 이름은 무엇인가요?"( 그렇다, C++ 의 RTTI 와 그것의 typeid, typeinfo 는 이름을 제공하지만, 그것은 보통 당신이 기대하거나 원하는 이름이 아니다. ). 만약 그 class 가 dynamic polymorphism 이 아니라면, 그 대답은 단지 "Foo" 일 것이다. 그러나 그것은 우리에게 단순하지 않다. 우리가 template 과 metaprogramming 을 사용한다고 해도 우리는 앞의 object 를 위한 선언을 살펴 보는 것만으로는 object 의 실제 type 을 알수 없을 것이다.


적절한 metadata system 을 가지고 있다면, 우리는 class를 ( 혹은 심지어 기본형까지 ) 표현하는 object 를 획득하게된다. 우리는 metaprogramming type traits 와 비슷하지만 더 많은 정보를 가지고 있고 그것이 실시간에 동작한다는 점은 다른 무엇인가를 가질 수 있다. 우리는 다음과 같이 코드를 작성할 수 있을 것이다.



Sample 에서 보여 주듯이 현재 내 project 에서 metadata 를 획득하는 것의 가장 큰 용도는 game object 의 data-driven runtime composition 인데, 이는 단순한 text file 을 사용해 매우 다양하고 복잡한 방식으로 game entity 들을 customize 할 수 있게 만들어 준다. 또한 우리는 그것을 configuration system 을 위해서 사용하기도 하고, debugging macro 로 사용하기도 하는데, 우리의 editor 는 그것을 proeprty 편집을 위해서 사용하기도 한다.


위의 성능 관련 질문들에 대해 간단하게 답하면, 내가 설명할 system 은 CPU time 과 memory 에 있어 매우 효율적이므로 별로 의미가 없는 질문이다. 심지어는 특정 API cleanliness 의 비용에서는 더 빠를 수도 있다. 나는 가장 쉬운 use approach 에 대해서 설명하면서, 가장 효율으로 변경될 수 있는 영역에 대해서도 언급할 것이다. 내 project 에서 나는 road approach 의 중심을 향해 갈 것이며, 그것은 여기에서 설명하는 것보다는 약간 다루기 어렵지만 더 좋은 효율성을 가진 API 를 가지고 있다. 


( 원문 : Touching briefly on the questions above about performance, it's worth noting that the system I'll be describing is highly efficient in terms of both CPU time and memory. It's possible to get even more efficiency at the cost of some API cleanliness. I'm going to describe the easier to use approach, but I'll note the areas where things could be changed to maximize efficiency. In my projects I lean towards a middle of the road approach, which has an API slightly more cumbersome than what I describe here, but which has a bit more efficiency. )


Metadata Class


우리가 필요로 하는 첫 번째 것은 metadata object 를 위한 실제 class 이다.( 그렇다, 이 글의 마지막까지 가면 우리 metadata system 은 자신을 설명할 수 있게 될 것이다. ) 특별히 복잡한 것이 필요하지는 않지만, 당장은 극단적으로 단순한 형태를 유지할 것이다. 우리 metadata system 은 object 의 type 과 size 를 출력하기에 충분한 정보만을 가지게 될 것이다. 다음 article 들에서는 그것의 기능을 확장할 것이다.



Metadata Registry


Metadata 의 핵심적인 기능 중 하나는 class 의 instance 를 먼저 얻지 않아도 이름으로 class 에 대한 metadata 를 찾아내는 능력이다. 예를 들어 이는 factory system 에 유용하다.


그 아이디어는 모든 Metadata object 를 관리하는 전역 system 이 존재해야만 한다는 것이다. 이를 동작하게 만드는 데는 여러 가지 방법이 존재하는데, 단순하지만 비효율적인 것부터 매우 복잡하지만 ( 실용적으로는 ) 비용이 들지 않는 것까지 다향하다. 나는 단순한 접근법에 대해서 다룰 것이다.


가장 기본적인 수준에서, Metadata object 들을 다루는 class 를 만들 필요가 있으며, 그 class 를 사용해 자신들을 등록하는 Metadata object 들을 가지고 있게 도니다. 그 class 는 초기화 순서 문제( order-of-initialization problem )을 피하기 위해서 static method 와 member 들만을 사용할 것이다( 그러므로 그것들은 원한다면 몇 개 안 되는 c-style 의 function 들과 global 들이 될 수 있다 ).



이를 위해 필요로 하는 것보다 더 많은 것들이 존재하지는 않는다. 그러나 이 system 을 위해서 std::string 이나 std::map 을 사용하는 것이 그리 효율적이지는 않다 . 특히 메모리 제약이 심한( memory-contstrained ) 환경을 위해서는 이들을 사용하는 것을 피하는 것이 좋을 수도 있다. 


std::string 의 사용을 피하는 것이 가장 쉽다. C-style 문자열 포인터를 사용하지 않고 Metadata 의 이름에 대한 복사본을 만들 이유가 없다. std::map 인스턴스를 위한 custom comparator 를 지정할 필요가 있지만, 그것은 가능하면 trivial exercise 여야 한다.


std::map 도 사용하지 않으려면 약간의 트릭이 필요하다. 우리는 Metadata object 에 대한 ( O(logN ) 보다는 나은 ) 효율적인 검색 비용을 유지해야만 한다. 그것은 우리가 그것들을 잘 동작하는 tree 에 넣기를 원할 것이라는 것을 의미한다. 내가 사용해 온 접근법은 실제로 내 tree 를 작성하는 것이며, 거기에서 Metadata object  들은 그 자체로 node 가 된다; that is, the Metadata class has the child pointers and such necessary to implement a binary tree ( or a red-black tree, or so on ). The consequence is a lot of extra code since std::map can't just be reused, but there are absolutely zero runtime memory allocations required for the metadata system, the memory cost of metadata is as good as or better than std::map, and the runtime complexity is the same


만약 compile-time string hashing 이 Visual C++ 에서 가능했다면, 우리의 "완벽한" hash 를 사용하는 고정 크기 hash table 을 사용할 수 있는 선택지가 있었을 것이다. 하지만 슬프게도 Visual Studio 11 SP1 까지는 ( Microsoft 가 결국 GCC 와 Clang 이 이미 가지고 있던 것과 같은 constexpr 지원을 하게될 때 까지는 ) 이 방식이 선택지가 될 수 없다.


만약 당신이 자신의 tree 를 구현하려고 한다면, 나는 template 을 섞은 형태를 추전한다. 왜냐하면 그것은 나중에 metadata 에 대한 같은 문제들을 가지고 있는 ( 그리고 당신이 단순한 방법을 선택한다면 Metadata 의 또 다른 std::map member 일 뿐인 ) property 를 붙일 때 유용할 것이기 때문이다.


등록에 대한 것을 마치기 전에, Metadata 의 생성자는 자신을 manager 에 등록시키기 위해 MetaManager::registerMeta( this )를 불러야만 한다.


Name-Based Singletons


우리는 Metadata class 를 가지고 있으므로 다음과 같은 질문을 할 수 있다. "어떻게 각 class 에 대한 Metadata object 의 instance 를 생성해야 하나요?". 이를 위해서는 여러 가지 방법을 사용할 수 있다. 나는 다른 engine 들에서 볼 수 있는 방법들을 거의 대부분 사용해 보았고, 그것들은 장단점을 가지고 있다. 단순한 기법으로 시작해서 내가 최고라고 느끼는 것을 만들어 보자.


첫 번째 방식은 각 class 에 대한 전역 변수를 만드는 것이다. 예를 들어 내가 GameObject 라는 class 를 가지고 있었다고 한다면, 나는 g_MetaGameObject 라는 전역 변수를 가지고 있을 것이고, 그것은 Metadata 의 instance 이다. 단순하다. 몇 개의 매크로가 이러한 전역 변수들에 대한 선언, 정의, 참조를 쉽게 만들어 준다.



끝내주게 간단한다. 마지막 매크로를 제외하면 말이다. 그것은 좀 문제가 있으며, 이 방식을 내가 추전하지 않는 큰 이유이다. 특정 object 를 위한 metadata 를 실제로 획득하기 위해서, 그 object 는 getMetadata() 와 같은 method 를 가지고 있어야만 할 것이라는 것이다. 이는 요구사항이자 의무사항이다. 공통 base class 가 존재하지 않으며, 단지 metadata 를 사용하는 모든 class 가 getMetadata() 라는 method 를 가지고 있다. 그리고 그것은 class 를 위한 Metadata 의 pointer 를 요구한다. 



이는 다른 매크로를 추가해 약간 단순화될 수 있다. 이 method 는 virtual 이 아니며 상속이 필요없다. 그것은 자식을 가지고 있는 class 들에 대해서는 virtual 을 필요가 있으며, 그래야 META 매크로가 base pointer 상에서 불릴 때 기대하는 동작을 할 수 있다. 나는 그것을 단순하게 유지할 것이며, 단지 dynamic class 들을 위한 매크로의 버전을 보여줄 뿐이다.



이 방식은 세 가지 큰 문제가 있다. getMetaData() 라는 method 를 요구한다는 것이다. 다른 방식들이 dynamically polymorphic object 들에 대해 그것을 요구하는 반면 ( 하나의 우회방법이 있지만 비용이 크다 ), 그것은 metadata 에 참가하기 위해 class 를 수정하거나 확장할 필요가 없다. 특히 그것은 third party library 나 기본형과 같은 수정될 수 없는 class 들에 metadata 를 추가하는 것이 불가능하다.


두 번째 큰 문제는 이 방식이 template 내에서 동작하지 않는다는 것이다. 왜냐하면 compile-time metadata lookup 은 ( 적절한 metadata 전역 변수의 이름을 생성하기 위해서 ) type 의 이름에 대한 정보를 필요로 하는데, template 은 그것을 가지고 있지 않기 때문이다.


세 번째 큰 문제는 namespace 를 가진 type 이나 template parameter 를 가진 type 들이 metadata 를 가질 수 없다는 것이다. 왜냐하면 이러한 object 들을 위한 이름을 생성할 방법이 없기 때문이다. MyNamespace::MyClass 와 같은 class 식별자를 위의 macro 중 하나에 넘기면 부적절한 code 를 생산하게 될 것이다. MyTemplate 과 같은 type 을 넘기려고 시도해도 같은 일이 발생한다.


Template-Based Singletons


내가 발견한 두 번째이자 마지막 방식은 metadata 를 정의하고 검색하기 위해 template 을 사용하는 것이다. 여기에서 이 아이디어는 templated type 이 static method 와 static member 를 가질 수 있다는 데서 출발한다. 다른 모든 static member 와 static local variable 들이 수행하는 것과 같은 규칙에 의해, a statically allocated in a template will exist once and once only in the problem( the One Definition Rule guarantees this ).


즉 metatdata 를 생성하기 위해 새롭고 단순한 templated type 과 매크로 집합을 생성하는 것이 가능하다는 것이다. 이 template 은 이전 방식에서의 몇 가지 단점들을 제거해 준다. 먼저 그것은 모든 type 을 위한 metadata 를 찾는 것을 허용하는데, type 의 이름이 아니라 type 에 대한 compiler 의 정보에 의존한다. 그래서 그것은 template 에서 작동한다. 두 번째로 각 class 를 위한 유효한 전역 식별자들을 생성할 필요가 없기 때문에, 그것은 namespace  안에 존재하거나 template parameter 를 가지고 있는 class 들을 쉽게 지원하고, class 를 확장할 필요 없이 사용될 수 있으며, 심지어는 int 나 float 과 같은 기본형과 함께 사용될 수 도 있다. 마지막으로 나중에 살펴 볼 template metaprogramming 의 일부 기능은 dynamic polymorphism 이 문제가 되지 않는 모든 경우에 대해 object 에 대한 getMetadata() method 를 필요하지 않게 만들어 준다.



마지막 매크로는 아마 약간의 설명이 필요할 것이다. 우리가 object 를 가지고 있다고 하자. decltype 이라 불리는 C++ 11 의 기능은 우리가 object 의 type 을 획득할 수 있도록 해 준다. 이는 쓸모가 없어 보이지만( 우리가 object 를 가지고 있다면, 우리는 그것의 type 을 어디엔가는 선언해야만 하고, 결국에는 그래서 우리는 그것이 무엇인지 이미 알고 있을 것이다 ), type 을 복사하기 보다는 매크로에 object 의 이름을 그냥 넘기는 것이 훨씬 더 편리하다. 특히 길고 보기 싫은 templated 이름들에 대해서는 더욱 더 그렇다.


META 매크로에 대한 이 구현은 한 가지 단점을 가진다; 그것은 dynamic polymorphism 을 사용할 경우 전혀 작동하지 않는다는 것이다. 만약 MyBase 에 대한 pointer 를 가지고 있지만 instance 는 실제로는 MyDerived 를 참조하고 있다면, META 에 의해 반화되는 Metadata instance 는 MyBase 의 instance 일 것이다. 좋지 않다. 우리는 이 기사의 나중 부분에서 이를 수정할 것이다.


당신은 아마 내가 MetaManager 에서 했던 것과는 다르게 MetaSingleton 에서의 singleton Metadata 를 구현했다는 것을 깨달았을 것이다. 그것은 두 가지 이유 때문이다. MetaManager::getMata() method 에서 찾을 수 있는 static local variable 은 보통  내부적으로 구현되는데, 이를 위해서 각 static local variable 을 위한 hidden global boolean 을 가지고 있으며 함수가 불릴 때마다 그 boolean 이 true 로 설정되었는지를 검사한다.bool 과 같은 global 혹은 primitive type 들은 항상 코드 실행 전에 0 으로 초기화되므로 hidden boolean 은 항상 getMetas() method 가 처음 불릴 때 false 인 것이 보장된다. 심지어는 global object 의 어떤 random constructor 에서 불린다고 해도 마찬가지이다. 그 첫 번째 호출에서 그 boolean 은 false 이므로 그 object 는 초기화되 후에 그 boolean 을 true 로 설정하기 때문에, static local object 가 단 한 번만 그리고 첫 번째 사용될 때만 초기화되는 것을 보장한다. 그러나 그 boolean 은 method 가 불릴 때마다 검사된다. metadata object 들은 일반적으로 game 의 성능에 민감한 부분에서 접근되지 않지만, 나는 불신자들을 위해 복잡한 metadata system 을 사용하고 있을 때 조차 raw bare-metal efficiency 를 입증하고자 했다.


Static member 를 사용하는 두 번째 이유는 그것이 그 member 가 명시적으로 cpp 파일에서 초기화되는 것을 허용한다는 것이다( 사실은 요구되는 것이다 ). 당장은 Metadata object 에서 class 이름을 설정하기 위한 초기화 동안에만 매크로를 필요로 한다. 내가 object 에 property 를 추가하는 방법과 다른 진보된 실시간 초기화를 수행하는 방법에 대해서 보여 주는 이 기사의 나중 부분에서 명시적인 초기화를 할 필요가 생길 것이다.


Robust Compile-Time Lookup via Partial Template Specialization


We've got a major problem in our template approach right not. 우리가 MetaSingleton<> 에 접근할 때마다 우리는 어떤 qualified type name 을 넣고 있다. Template 이 작동하는 방식 때문에, MetaSingleton 과 MetaSingleton 과 MetaSingleton 은 모두 다르다. 그리고 결국 자신만의 Metadata object 를 가지게 된다. 우리는 실제로는 그것을 원하지 않는다.


이 문제를 해결하기 위해서 두 가지 방법이 존재한다. 첫 번째는 std::remove_const<>, std::remove_reference<> 등과 같은 특별한 metaprogramming template 들을 사용하는 것이다. 그것들은 사용하기 쉽지만, 다음과 같은 경우에는 올바르게 사용하기 어렵다. 만약 우리가 "MyType const* const&" 과 같은 qualified type 을 가지고 있다면, 우리는 unqualified "MyType" 까지 내려가기 위해 그것을 std::remove* 용법들에  매우 깊게 넣어야만 할 것이다. 감사하게도 부분 template 특수화가 사용하기 더 쉬우며, 그것은 여기에서의 우리의 모든 문제들을 해결한다.


부분 template 특수화는 unqualified 버전만을 참조하는 MetaSingleton 에 대한 대안 버전을 생성할 수 있도록 해 준다. 최종 결과는 우리가 template parameter 에 넘기는 것이 무엇인지와 관계없다. 단지 unqualified 버전의 static Metadata instance 만이 계속 참조될 것이다. 이는 구현하기 어렵지 않다. 단지 타자를 좀 쳐야 할 뿐이다.



const 버전의 참조와 pointer 에 대한 특수화가 요구된다는 것에 주목하라.


독자를 위한 퀴즈가 있다; 완벽함을 위해, "volatile" 에 대한 특수화가 필요한가? "static" 과 "register" 에 대한 것은 어떻게 해야 하는가? ( Hint : C++ 에서 type qualifier 와 storage class 의 차이는 무엇이고, 그러한 어떤 keyword 들이 그러한 카테고리들로 나뉘었는가? 그리고 왜인가? )


Supporting Polymorphic Classes


우리 META 매크로는 여전히 dynamic polymorphism 을 지원하지 않는다. 우리는 우리가 관심을 가지고 있는 실제 object 의 type 을 위한 Metadata instance 를 획득할 수 있는 어떤 방법을 필요로 하며, 그것은 base type 에 대한 pointer 나 reference 를 사용해 우리가 그것을 접근하고 있는지 여부와 상관이 없어야 한다.


불행하게도 우리 object 상에 virtual getMetadata() method 를 필요로 하는 상황으로 돌아간다. 하지만 다행히도 template 방식을 사용하면, 이 method 는 dynamic polymorphism 이 무넺가 될 때만 필요하다. 또한 template 방식은 virtual method 를 생성하기 위해 template mixin 의 사용을 허용하며, 그것은 우리가 전에 사용했던 매크로보다 쉽고 이쁘다.



이는 단지 단순한 CRTP( Curiously Recursive Template Pattern ) 응용프로그램일 뿐이다.



이것이 더 이쁜지에 대해서는 논란이 있다. 나는 "real" base type 을 mixin template 의 parameter 로 만들어야 하는 것을 특별히 좋아 하지는 않는다. 하지만 대안이 없다. 반대로 매크로로 표현하면 다음과 같다 :



둘 다 잘 동작할 것이다( 그리고 동일한 레이아웃과 실행 코드를 생성해야만 한다 ). 그러므로 당신이 더 이해하기 좋고 보기 좋아 보이는 것을 사용하면 된다. 당신이 C++ 의 내장 RTTI 를 사용한다면, 그 template mixin 방식은 당신의 RTTI table 을 부풀리게 될 것이다. 그래서 그것은 당신이 매크로 방식을 사용하도록 만드는 결정 요인이 될 수도 있다.


Template Metaprogramming for Metadata Lookup


Template metaprogramming 은 우리에게 getMetaData() method 를 선택적으로 호출할 수 있도록 하는 기능을 제공해 준다. 그것은 보기 싫고 끔찍하고 정신착란을 일으키는 기교임에도 불구하고 아직도 사용중인 것이다. 이 시리즈의 다른 기사는 과도한 수동 개입 없이 Metadata object 의 기능을 확장하는 metaprogramming 을 사용할 것이다. 그래서 우리는 이 주제에 대해서 다뤄야만 할 것이다. Time to rip off the bandaid. 그냥 코드를 보자.



자. 매우 보기 싫다. 명확하지도 않다. There's an even uglier way to do this, and there's a lot of ways that one would think should work but won't ( with Visual Sutdio, at least ).


나는 여기에서 metaprogramming  의 핵심 기교에 대해 설명할 것이다. 만약 template metaprogramming 에 익숙하지 않다면, Modern C++ Design 이라는 책을 추천한다. The topic is big enough that I simply can't do it justice here, at least not without giving it an entire large article all to itself. 


MetaIsDynamic 으로 돌아 가 보자. 우리는 다음과 같이 정의된 boolean 결과를 가지고 있다 :



이상한 부분은 std::is_same, no_return, decltype, check( 0 ) 등을 사용하는 모든 mess 이다. 위에서 언급했듯이 우리는 내부적으로 type comparison 을 수행하고 있다. check( 0 ) 을 사용하는 것은 두 개의 서로 다른 return type 중의 하나를 가지게 될 함수 호출을 평가할 것이다; 만약 MetaType 이 getMetadata() method 를 가지고 있지 않다면, check<>() 함수의 return type 은 MetaIsDynamic 에 정의되어 있는 no_return 구조체일 것이다. 만약 MetaType 이 getMetaData() method 를 가지고 있다면, 반환값은 no_return 이 아닌 다른 무엇인가일 것이다. 나는 decltype 과 std::is_same template 을 사용해 return type 을 검사하고 있다. 전체 라인은 "만약 no_return 이 check( 0 ) 의 반환값과 같은 type 이라면 boolean 값은 false 이며, 그렇지 않으면 true 이다"  라고 읽혀질 수 있다.


그리고 나서 중요한 질문은 check<>() 의 return type 이 선택되는 방법이 무엇이냐이다. 이는 Substitution Failure Is Not An Error ( SFINAE ) 라는 template behavior 를 사용해 수행된다. 그러므로 우리는 모호하지 않은 두 개의 templated 버전의 함수가 존재하도록 정렬할 수 있다( 왜냐하면 그것은 에러, SFINAE 나 no 일 것이기 때문이다 ). 그리고 여기에서 그것은 우리가 지정한 ( templated type 이 지정된 method 를 가지고 있는지 여부와 같은 ) 어떤 조건에 대해 유효하지 않을 것이다.


그 기교를 사용하기 위해서, MetaIsDynamic 구조체는 두 가지 버전의 check<>() 함수를 가지고 있다.



첫 번째 버전의 check<>() 를 살펴 보자. 그것은 (  우리 "true" 값인 ) char 를 반환하며, and takes... some kind of mess. 매개변수 type 은 확실히 불쾌하다. 그러나 그것은 그것의 component 부분까지 쪼개 가면 그렇게 나쁘지는 않다. 매개변수는 끝 부분에의 별표( * )를 통해 확인할 수 있듯이 pointer 이다. 그것은 표현식의 decltype 에 대한 pointer 인데, 그것은 그것이 expression 이 평가하고 있는 모든 type 에 대한 pointer 임을 의미한다. 그 표현식은 어떤 pointer cast 상에서 getMetadata() 를 호출한 결과이며, decltype 은 getMetadata() 의 return type 이 무엇이든간에 평가하게 된다. Pointer cast 는 0 ( 다른 말로 NULL ) 으로부터터 template type U 로의 cast 이다. 그것은 단지 method 를 호출하는 "random" pointer 를 생성하는 쉬운 방법일 뿐이다. 왜냐하면 우리는 method 를 사용하는 표현식을 필요로 하며, 그것은 object 를 요구하기 때문이다. decltype 은 단지 expression 의 type 에서만 보이며 결코 실제로 expression 을 평가하지는 않기 때문에, 우리가 사용한 pointer 는 유효한 object 일 필요는 없다. 그래서 NULL 을 우리가 원하는 type 으로 casting 하는 것은 만족스럽다. 그래서 전체 매개변수는 "U::getMetadata() 의 return type 에 대한 pointer" 라고 읽혀질 수 있다.


어떤 사람은 "그렇지만 만약 type U 가 getMetadata() method 를 가지고 있지 않으면?" 이라고 질문한다. 자 그것은 compiler 가 check<>() 의 선언을 평가할 때 대답해야만 하는 정확한 질문이다. 그 매개변수 type 을 생성하기 위해서 그 표현식이 사용되었기 때문에, 매개변수 type 이 실제 유효한 type 이거나 substitution failure 가 존재하기 때문에, 이는 SFINAE 가 효과가 나타나는 곳이다. 만약 typename U ( 이는 항상 MetaType 과 같은 type 이다 ) 가 getMetadata() method 를 가지고 있지 않다면, check<>() 함수 선언은 그냥 버려지며 SFINAE 규칙에 의해 에러 없이 무시된다. 이것이 정확히 우리가 원하는 것이다.


두 번째 버전의 check<>() 는 단지 대비책일 뿐이다. 그것의 매개변수는 C++ 에서의 resolution precedence 때문에 ellipse 들이다. 그것은 pointer 인자를 받아들이지만, 첫 번째 버전의 check<>() 가 존재한다면 항상 그것의 의미가 명확해지도록 보장하기도 한다.


제출된 코드에서의 한 가지 실수는 그 type 상의 getMeatadata() 가 실제로 Metadata 에 대한 pointer 를 반환하는 것이 보장되지 않는다는 것이다. 그것을 검사하는 노력까지 할 수 있음에도 불구하고, 나는 그럴 필요가 없다고 주장하고 싶다; Metadata 를 획득하지 않는 "getMetadata" 라 불리는 method 들을 추가하는 것과 같은 바보같은 짓은 하지 말기 바란다. 문제는 없을 것이다.


이 새로운 MetaIsDynamic metaprogramming 구조체를 실제로 사용하는 것은 약간 더 많은 작업을 해야 한다. 새로운 template 특수화 집합들이 필요하며, 이제 다 된 것이다.



std::enable_if 를 사용함으로써 약간 더욱 metaprogramming 이 들어 갔다. 그것은 SFINAE 에 의존하는 작은 기교인데, which allows for a version of a function to exist based on some boolean constant. Boolean 상수는  우리 MetaIsDynamic 에 의해 생성된 것과 비슷하다. resolve<>() 함수 중 한 버전은 getMetadata() method 를 호출할 것이며, 다른 버전은 그냥 MetaSingleton 을 직접적으로 사용할 것이다.


이제 우리는 그것을 보기 좋고 사용하기 쉽게 만들기 위해 새로운 버전의 META 매크로를 필요로 한다.



decltype 은 실제로 그것의 인자를 평가하지 않는다는 것을 다시 기억하라. 그래서 'obj' 매크로 매개변수를 반복적으로 사용하는 것은 전처리기 매크로의 일반적인 이중 확장 문제에 위배되지 않는다.


It's worth nothing that when compiled with optimizations on, all of this metadata lookup code will compile away to either a trivial address lookup of a global object or ( for classes that have a virtual getMetadata() method ) a single virtual call to a function that simply returns the address of a global object. Stepping through the code in debug builds can be a little scary, though, as it may seem like there are hobajillion recursive calls ( especially when those lookup templates get hit ), but don't be fooled by how bumb a compiler is with all of its optimizations turned off.


Usage Examples


Metadata class 는 많은 것을 하지 않는다는 것에 유념하며, 여기에 system 이 사용될 수 있는 방법에 대한 몇 가지 예제를 수록한다. 내부적인 부분과 구현 세부사항들이 있었지만, 결국 가장 중요한 것은 그 작업들이 할 수 있는 것이 무엇인지 확인하는 것이 좋다. 



컴파일러와 표준 라이브러리 제작자에 의존해 다양한 크기 출력을 얻을 수 있음에 주의하라.


The Future


이 시리즈의 다음 기사에서 나는 Metadata class 를 더욱 유용하게 만드는 것에 대해서 살펴 볼 것이다. 특히 우리는 복제, 단순한 상속 검사( object 의 instance 를 요구하지 않는 dynamic_cast<> 를 생각해 보자 ), 정렬과 같은 부가적인 class attribute 들 혹은 is_abstract flag, 그리고 완벽한 getter/setter 에 의해 지원되는 named/typed class property 들을 포함하는 factory method 들을 추가할 것이다. 시간이 있다면 script language binding 을 생성하기 위한 method binding 에 대해서도 살펴 볼 것이다( 그건 큰 주제이다 ).


+ Recent posts