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 에 대해서도 살펴 볼 것이다( 그건 큰 주제이다 ).