원문 : Introducing the Universal CRT

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


 

작년 6월에 우리는 한 쌍의 기사들을 발표했는데, 그것은 Visual Studio 2010 에서 Visual C++ C Runtime( CRT ) 에 대해 수행된 큰 변화에 대해서 다뤘습니다. "The Great C Runtime (CRT) Refactoring" 에서, 우리는 CRT 에 대해 수행한 중요한 구조적 변화를 설명했습니다. 그리고 "C Runtime (CRT) Features, Fixes, and Breaking Changes in Visual Studio 14 CTP1" 에서, 우리는 기존에 구현해온 중요한 기능들과 우리가 만들어 온 행동 변화들을 모두 애뮬레이트했습니다.

 

우리는 그 기사들을 작성하고 Visual Studio 2015 의 첫 번째 Community Technology Preview (CTP) 를 릴리스한 이후로 몇 개월 동안 여러분으로부터 많은 피드백을 받아 왔습니다. 우리는 특히 Microsoft Connect 에 여러분이 제출해 준 많은 탁월한 버그들에 대해서 감사드립니다. 첫 번째 CTP 이후로 CRT 에 많은 변화를 주지는 않았지만, 우리는 여러분의 피드백을 다루고, 좀 더 나은 결과를 이루고, 일부 오래 걸리는 프로젝트들을 마무리하기 위해 많은 일들을 해 왔습니다. 가장 최근에 릴리스된 Visual Studio 2015 CTP6 는 우리가 작업해 온 이러한 모든 개선점들을 포함합니다. 우리는 또 다시 한 쌍의 기사들에서 이러한 변화들에 대해서 다룰 계획입니다: 이 기사는 첫 번째 CTP 이후로의 중요한 구조적 변화들에 대해서 다룹니다; 다음 기사에서는 새로운 기능들, 버그 픽스들, 그리고 변화에 대한 세부사항을 모두 애뮬레이트할 계획입니다.

 

지난 6월의 기사들에서, 우리는 CRT 가 두 개의 논리 파트들로 나뉜 방식에 대해서 설명했습니다: VCRuntime 은 프로세스 시작과 예외 처리와 같은 것들을 위해서 요구되는 기능성들을 지원하는 컴파일러를 포함하고, "stable" 파트는 CRT 의 순수한 라이브러리 파트들을 포함합니다. 여기에서 우리는, Visual Studio 의 주요 버전과 함께 새롭게 버전이 설정되는 DLL 들을 릴리스하기 보다는, 앞으로는 in-place 로 서비스를 할 것입니다. 이 시점에 이 "stable" 파트는 두 개의 라이브러리 형태를 취했었습니다: AppCRT 와 DesktopCRT( 릴리스 DLL 들의 이름은 appcrt140.dll 과 desktopcrt140.dll 이었습니다 ).

 

VCRuntime 은 여전히 같은 형태로 존재하며, 기존의 CTP 들에서와 동일한 내용을 가지고 있습니다. 이 마지막 CTP6 에서 우리가 만든 중요한 변화들은 "stable" 파트 내부에 존재합니다. AppCRT 와 DesktopCRT 는 하나의 라이브러리로 다시 합쳐졌는데, 이는 Universal CRT 라 불립니다. 새로운 DLL 들은 ucrtbase.dll( 릴리스 ) 와 ucrtbased.dll( 디버그 ) 입니다; 그것들은 버전 넘버를 가지고 있지 않습니다. 왜냐하면 in-place 로 서비스될 것이기 때문입니다.

 

Universal CRT 는 윈도우즈 운영 체제의 요소입니다. 이것은 January Technical Preview 의 시작과 함께 Windows 10 의 일부로 포함되어 있으며, 윈도우즈 업데이트를 통해서 이전 버전의 운영 체제에서도 이용할 수 있습니다.

 

Universal CRT 를 사용하여 소프트웨어 빌드


이전에는 CRT 의 모든 헤더, 소스, 라이브러리가 Visual C++ SDK 의 일부로서 배포되었는데, 이는 Visual Studio 설치 디렉토리( 일반적으로 C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC )의 VC 서브 디렉토리에 설치되었습니다. VCRuntime 을 위한 파일들은 여전히 Visual C++ SDK 의 일부로 존재합니다. 헤더, 소스, 라이브러리는 이제 개별적인 Universal CRT SDK 의 일부로 배포됩니다. 이 SDK 는 Visual Studio 에 포함되어 있습니다; 이는 기본적으로 C:\Program Files (x86)\Windows Kits\10 에 설치됩니다. 디버그용 ucrtbased.dll 은 여전히 이 SDK 의 일부로 포함되며, 시스템 디렉토리에 설치됩니다.

 

우리는 Visual C++ MSBuild props 및 targets 파일들을 업데이트하여 새로운 Universal CRT 디렉토리들을 include 및 library 경로에 추가했습니다. 만약 Visual Studio 2015 로 프로젝트를 생성하거나 Visual Studio 2015 로 현존하는 프로젝트를 업그레이드한다면, 그것은 이러한 새로운 디렉토리들을 자동으로 선택할 것입니다. 만약 Visual C++ MSBuild props 및 targets 를 사용하지 않거나 기본 include 및 library 경로를 그런 props 및 targets 파일에서 상속하지 않는 프로젝트를 업그레이드한다면, 여러분은 그 새 디렉토리들을 수동으로 업데이트해 줄 필요가 있습니다. 여러분은 Universal CRT SDK 파일들을 찾기 위해서 다음과 같은 MSBuild 속성들을 사용할 수 있습니다.

 

$(UniversalCRT_IncludePath)
$(UniversalCRT_LibraryPath_x86)
$(UniversalCRT_LibraryPath_x64)
$(UniversalCRT_LibraryPath_arm)

 

그러므로 여러분이 /nodefaultlib 옵션을 사용해 링크하지 않는 이상, 모든 correct 라이브러리 파일들은 여러분이 프로젝트를 링크할 때 검색될 것입니다. 만약 /nodefaultlib 옵션을 사용해서 링크한다면, 여러분은 링크를 할 때 몇 개의 추가적인 라이브러리들을 링크할 필요가 있을 것입니다. 예를 들어, 이전에는 CRT DLL 을 사용하기 위해서 msvcrt.lib 를 가지고 있었다면, 이제는 vcruntime.lib 와 ucrt.lib 도 링크할 필요가 있습니다. 아래 테이블에서는 어떠한 라이브러리가 이전의 라이브러리에 대응해 링크되어야 하는지를 보여 줍니다.

 

 Release DLLs  (/MD  ) :

 msvcrt.lib

 vcruntime.lib

 ucrt.lib

 Debug DLLs    (/MDd) :

 msvcrtd.lib

 vcruntimed.lib

 ucrtd.lib

 Release Static (/MT  ) :

 libcmt.lib

 libvcruntime.lib

 libucrt.lib

 Debug Static    (/MTd) :

 libcmtd.lib

 libvcruntimed.lib

 libucrtd.lib

 

Universal CRT 를 사용하는 소프트웨어 배포하기


과거에는, "Deployment in Visual C++" 에서 설명된 많은 방식 중 하나를 사용해서 Visual C++ 라이브러리들을 소프트웨어와 함께 배포해 왔을 것입니다. Universal CRT 를 제외한 모든 Visual C++ 라이브러리들에 대해, 배포 방식에서의 변화는 있을 수 없습니다. 배포 모드가 무엇이든 간에( central, local or static linking ) 이전 방식이 여전히 사용될 수 있습니다.

 

그러나 위에서 언급했듯이 Universal CRT 가 윈도우즈 운영 체제로 이동했기 때문에, 약간의 주목해야할 변화들이 존재합니다:

 

  1. Universal CRT 는 윈도우즈 운영 체제 요소입니다. 이는 윈도우즈 10 의 일부입니다. Windows 10 보다 앞선 버전의 윈도우에 대해서도, 윈도우즈 업데이트를 통해서 Universal CRT 가 배포될 수 있습니다. 윈도우즈 Vista 부터 Windows 8.1 까지를 위한 윈도우즈 업데이트 MSU 패키지들이 있습니다. 현재 MSU 패키지들은 VCRedist 설치의 일부로서 설치됩니다. 이후의 Visual Studio 2015 빌드에서는, 이러한 MSU 패키지들이 Universal CRT SDK 의 일부로서 개별적으로 배포될 계획이며, 다운로드를 위해서 support.microsoft.com 을 이용할 계획입니다.
  2. 만약 Universal CRT 가 설치되어 있지 않은 윈도우즈 운영 체제( 즉 윈도우즈 8.1 이전 )에서 사용되도록 설계된 소프트웨어를 빌드했다면, 여러분의 소프트웨어는 Universal CRT 를 설치하기 위해서 위에서 언급했던 윈도우즈 업데이트 패키지에 의존해야할 필요가 있을 것입니다.
  3.  만약 현재 VCRedist( 우리의 재배포 패키지 파일들 ) 를 사용하고 있다면, 기존에 그랬던 것처럼 잘 동작할 것입니다. Visual Studio 2015 VCRedist 패키지는 위에서 언급했던 윈도우즈 업데이트 패키지를 포함하고 있어서, 단순히 VCRedist 를 설치하는 것만으로도 Visual C++ 라이브러리들과 Universal CRT 를 설치할 것입니다. 이는 우리가 추천하는 배포 메커니즘입니다. 윈도우즈 XP 에 대해서는 Universal CRT 윈도우즈 업데이트 MSU 가 존재하지 않으므로, VCRedist 가 Universal CRT 자체를 배포할 것입니다.
  4. 만약 현재 Visual C++ 라이브러리들을 정적으로 링크하고 있다면, 기존에 그랬던 것과 같이 잘 동작할 것입니다. 우리는 성능 및 서비스 유용성을 이유로 Visual C++ 라이브러리들에 대한 정적 링크를 강력하게 권장하고 있습니다. but we recognize that there are some use cases that require static libraries and we will continue to support the static libraries for those reasons.
  5. Universal CRT 에 대한 머지( merge ) 모듈은 존재하지 않을 것입니다. 만약 현재 CRT 머지 모듈을 사용하고 있고, 여전히 Visual C++ 라이브러리들을 중앙에서 배포하기를 원한다면, 우리는 여러분이 위에서 언급된 윈도우즈 업데이트 패키지나 VCRedist 로 이동할 것을 권장합니다. 대안적으로, 여러분은 Universal CRT 와 Visual C++ 라이브러리를 정적으로 링크하는 선택을 할 수도 있습니다.
  6. Universal CRT 에 대한 app-local 배포가 지원됩니다. app-local 배포를 위한 바이너리를 획득하고자 한다면, Windows Software Development Kit (SDK) for Windows 10 을 설치하십시오. 이 바이너리들은 C:\Program Fiels (x86)\Windows Kits\10\Redist\ucrt 에 설치될 것입니다. 여러분은 모든 DLL 들을 당신의 app 에 복사할 필요가 있습니다( DLL 집합은 서로 다른 버전의 윈도우즈에 대해서 다릅니다. 그러므로 지원되는 모든 버전의 윈도우에서 프로그램이 실행되도록 하기 위해서는 모든 DLL 들을 포함시켜야만 합니다 ).

이전에 언급했던 것처럼, 우리는 리팩토링된 CRT 를 CTP1 에서 소개한 이후로 많은 버그를 해결하고 기능을 개선해 왔습니다. 이번 주 후반에는 이러한 변경들에 대해서 더 세부적으로 다루는 두 번째 기사를 작성할 계획입니다. 그 동안에, 우리는 새로운 Universal CRT 에 대한 여러분의 피드백을 기대하도록 하겠습니다.

 

James McNellis and Raman Sharma

Visual C++ Libraries

원문 : 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.


원문 : 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