Traits
: template 에 전달되는 type 의 속성에 접근하는 기법
printv 함수에 전달된 v가 pointer 일 경우,
주소 뿐만 아니라 값도 출력하려면 어떻게 해야 할까?
T가 포인터인지 아닌지를 판단할 수 있으면 되지 않을까?
먼저 T type을 인자로 받는 primary template 을 선언한다.
내부에는 enum 값을 초기화해서 구조체 외부에서도 접근이 가능하도록 한다.
우리는 T type이 포인터인지 아닌지를 구별하는 것이 중요하므로,
Partial specialization(부분 전문화)를 이용해서 T* type을 인자로 받는
template 을 선언한다.
위예제에서
primary template 은 false 를 반환
partial template 은 true를 반환할 것이다.
결과적으로 IsPointer<T>::value 는 전달되는 T type에 따라서
포인터 인지 아닌지 true or false로 나뉘게 되고
이에 따라 분기하여 처리할 수 있게 된다.
이것이 traits 기법의 핵심이다.
또한, main함수에서 foo(&n); 만 호출했다면
IsPointer<T>::value 의 값은 complie time 에 정해져 버린다.
따라서 else 블록 안의 cout << "Not Pointer!!" << endl; 코드는 컴파일 최적화에 의해 사라지게 된다.
만약 printv(&n); 코드 없이 printv(n); 만 있다면 error 가 발생하는 이유는 무엇일까?
printv(n); 을 호출하면
template<typename int> void printv(int v) 로 코드 생성
IsPointer<int>::value 는 false 로 정해진다.
결국 if(false) 로 코드 생성.
그리고 컴파일러는 최적화전에 컴파일을 해서 코드에 이상이 없는지 확인을 한다.
그런데, cout << v << ", " << *v << endl; 코드에서
*v 는 int의 역참조이기 때문에 error 가 발생한다.
C++ 컴파일러가 동작하는 원리를 살펴보면,
1. 사용된 함수 T의 타입이 결정되면 실제 함수 code 생성
2. 생성된 함수 컴파일
3. 실행되지 않는 부분(code)은 최적화를 통해 제거
하지만 만약 printv(&n); code 도 활성화되었다면?
위에서 처럼 컴파일러가 IsPointer<t>::value 를 false로 컴파일 하여도
if-else 구문은 runtime 에 결정되는 실제 동작이 결정되므로,
컴파일러는 if-else 구문에 어떤 것이 호출될지 알 수 없어서,
2개의 template code 를 모두 생성하게 된다.
따라서 int type인 v를 *v 로 역참조하는 error가 발생한다.
결론적으로, if-else 구문으로 T type이 포인터인지 아닌지를 판별하지 않고
function overloading 을 사용한다.
Function overloading : compile time에 결정되며,
실제 호출되지 않는 함수 템플릿은 인스턴스화 되지 않는다.
if-else : runtime에 결정
IsPointer<T>::value 의 반환값은 pointer 인지 아닌지에 따라 true or false 이다.
true or false 는 int 로 0, 1 인데
overloading 을 사용하려면 type 이 달라야 하는데
0, 1 은 둘 다 int 이므로 같은 type이라 overloading 을 할 수 없다.
YES?, NO?를 어떻게 작성해야 할 지 생각해보자.
그전에 type이 구분된다는 것은 어떤 의미인가?
0, 1은 같은 int type 이라
foo(0), foo(1)은 같은 함수를 호출한다.
int2type<0>, int2type<1> 은 다른 type이 된다.
컴파일러가 컴파일 시에 int2type<0>, int2type<1> 2개의 다른 type에 따른 코드를 만들게 되고
foo(t0), foo(t1) 은 다른 함수를 호출한다.
이론적으로, int2type은 전달되는 N에 따라서 무궁무진하게 다른 type이 생성될 수 있다.
이 int2type 에 사용된 기법을 토대로
다양한 데이터 타입과 값에 대해 다른 type을 가지는 코드를 만들 수 있다.
int2type<>의 타입에 IsPointer<T*>::value 을 전달함으로써,
true(결국 1), false(결국 0)가 서로 다른 타입이 되게 된다.
그러므로, printvImp 함수 template도 2가지로 나뉠 수 있게 되어
function overloading 이 되는 것이다.
또한 printvImp 함수 템플릿에 전달하는 int2type<1>, int2type<0> 에는
최적화를 위해 임시 객체를 사용하도록 변수 이름을 적지 않아야 한다.
그래야 컴파일러가 임시 객체를 쓰려고 하는 의도를 파악하고
최적화에서는 사용만 하고 없애버린다.
그렇다면 int 의 값에 따른 type 구분뿐만 아니라 모든 데이터 타입에 적용할 수 있지 않을까?
위에서 얘기했던 traits 관련된 모든 기법이 C++11/14에 표준화되어 있기 때문에
type_traits header를 포함하면 사용할 수 있다.
추가적으로 traits 기법을 이용해서 알고리즘을 작성할 때, 주의해야 할 점은
if-else 구문을 사용할 경우, 위 예제와 같이 *(asterisk) 를 통한 포인터 접근 표현이 불가능하다.
하지만 function overloading 을 사용할 경우, 가능하다.
이번에는 pointer가 아닌 array type도 생각해보자.
배열의 이름은 자기 자신의 시작 주소를 나타내고, 주소(&) 값은 arr이나 &arr이나 동일하다.
하지만, C++에서는 int* pArr1 = &arr; 코드에서 에러를 발생시킨다.
배열명은 배열의 시작 주소라고 답하는 것은 30점짜리 답안이다.
배열명은 배열의 첫 번째 원소 시작 주소를 의미하는 상수 포인터이다.
배열명을 사용하면, 배열의 전체 타입이 첫 번째 원소의 타입으로 축소 또는 퇴화(decay)된다.
하지만 배열을 template으로 전달하여 사용할 경우, 퇴화(decay)되지 않는다.
결국엔 arr과 &arr 의 차이는 타입에 있다.
arr : 배열의 시작 주소, 타입은 첫 번째 원소
&arr : 배열의 시작 주소, 타입은 배열 전체
인자로 들어오는 타입이 배열인지 아닌지를 구분하는 코드를 구현해보자
2015년 9월 9일 수요일
[C++] Template specialization
Template specialization
: class template 을 arguments에 따라서 customizing 하는 기법
먼저 위 예제의 첫 번째 template 선언처럼
일반적인 template 선언은 primary template 이라 한다.
Specialization 은 2 가지로 나눌 수 있다.
1) T의 포인터 타입을 argument 로 받고 싶다면?
Partial specialization(부분 전문화)를 사용한다.
물론 T&, T&& 도 partial specializtion 을 이용한다.
2) 데이터 타입까지 정해진 argument 로 template 을 정의한다면?
template specialization 을 사용한다.
실제로 위에서 T* 에 대한 template code는 하나지만,
컴파일하면 기계어코드로는 int*, char* 2가지 code가 존재한다.
결국엔 template code 가 아무리 많아도,
컴파일 이후에 만들어지는 기계어 코드는 사용하는 class type의 개수에 의존적이다.
: class template 을 arguments에 따라서 customizing 하는 기법
먼저 위 예제의 첫 번째 template 선언처럼
일반적인 template 선언은 primary template 이라 한다.
Specialization 은 2 가지로 나눌 수 있다.
1) T의 포인터 타입을 argument 로 받고 싶다면?
Partial specialization(부분 전문화)를 사용한다.
물론 T&, T&& 도 partial specializtion 을 이용한다.
2) 데이터 타입까지 정해진 argument 로 template 을 정의한다면?
template specialization 을 사용한다.
실제로 위에서 T* 에 대한 template code는 하나지만,
컴파일하면 기계어코드로는 int*, char* 2가지 code가 존재한다.
결국엔 template code 가 아무리 많아도,
컴파일 이후에 만들어지는 기계어 코드는 사용하는 class type의 개수에 의존적이다.
[C++11] Lazy instantiation
Lazy instantiation
: 정의한 template 코드를 실제로 사용하지 않으면 컴파일러가 type에 맞는 변형된 code 를 생성하지 않는다.
AAA aaa; 컴파일 하면 왜 에러가 나는 것일까?
AAA class 의 void foo(int a); 함수에서 int a 를 *a 로 역참조하기 때문이다.
하지만 BBB bbb; 는 컴파일 하면 에러가 발생하지 않는다.
왜 일까?
template 은 정확히 class 가 아니라 틀(template)이기 때문에
실제 template 내의 코드(함수)를 사용하지 않으면 컴파일러가 변형된 code를 생성하지 않는다.
이것 이 "Lazy instantiation" 개념이다.
만약 main() 함수에서 아래와 같이 호출하는 code가 있었다면,
bbb.foo(0); //error. 실제 function 을 호출하기 때문에 error 발생
그 때는 class BBB 의 void foo(int a); code 가 생성되므로
역참조에 대한 컴파일 에러를 발생시키게 된다.
: 정의한 template 코드를 실제로 사용하지 않으면 컴파일러가 type에 맞는 변형된 code 를 생성하지 않는다.
AAA aaa; 컴파일 하면 왜 에러가 나는 것일까?
AAA class 의 void foo(int a); 함수에서 int a 를 *a 로 역참조하기 때문이다.
하지만 BBB
왜 일까?
template 은 정확히 class 가 아니라 틀(template)이기 때문에
실제 template 내의 코드(함수)를 사용하지 않으면 컴파일러가 변형된 code를 생성하지 않는다.
이것 이 "Lazy instantiation" 개념이다.
만약 main() 함수에서 아래와 같이 호출하는 code가 있었다면,
bbb.foo(0); //error. 실제 function 을 호출하기 때문에 error 발생
그 때는 class BBB
역참조에 대한 컴파일 에러를 발생시키게 된다.
[C++11] typename
typename
: template 정의 내부에서, 의존적인 이름이 type 임을 선언하는데 typename을 사용한다.
위 코드에서 DWORD가 의미하는 바는 무엇일까?
추측 1) class AAA 내부에 static int DWORD 를 말하는가?
C++ 문법에서 classname:: 이 의미하는 바는 class 내의 static variable 에 접근할 때 사용되며,
위 코드는 class AAA에 선언된 static variable 과 global variable p 간의 곱셈 연산일 수 있다.
추측 2) class AAA 내부에 int 를 typedef 한 DWORD 를 말하는가?
int 를 typedef 한 것이라면, int* p; 포인터로 해석할 수도 있다.
결론은, 컴파일러가 1 번째 추측, 2 번째 추측 인지 판단할 수 없어서
추측 1) static variable 접근으로 동작한다는 것이다.
따라서, template 내부에서 의존적인 타입에 대해 접근할 때는 typename 을 사용해야 한다.
: template 정의 내부에서, 의존적인 이름이 type 임을 선언하는데 typename을 사용한다.
위 코드에서 DWORD가 의미하는 바는 무엇일까?
추측 1) class AAA 내부에 static int DWORD 를 말하는가?
C++ 문법에서 classname:: 이 의미하는 바는 class 내의 static variable 에 접근할 때 사용되며,
위 코드는 class AAA에 선언된 static variable 과 global variable p 간의 곱셈 연산일 수 있다.
추측 2) class AAA 내부에 int 를 typedef 한 DWORD 를 말하는가?
int 를 typedef 한 것이라면, int* p; 포인터로 해석할 수도 있다.
결론은, 컴파일러가 1 번째 추측, 2 번째 추측 인지 판단할 수 없어서
추측 1) static variable 접근으로 동작한다는 것이다.
따라서, template 내부에서 의존적인 타입에 대해 접근할 때는 typename 을 사용해야 한다.
[C++11] std::Initializer_list
std::initializer list
: 동일 타입의 객체 list
함수 인자로 initializer_list<> 가 사용되는 경우를 살펴보자.
C++98/03에서는 불가능 했던
foo({1, 2, 3, 4, 5}); 가 지원되는 점이 눈여겨 볼 점이다.
생성자에 사용되는 initializer_list 를 살펴보자.
일반적인 생성자 보다 initializer_list<> 가 우선시 되어 호출된다.
: 동일 타입의 객체 list
함수 인자로 initializer_list<> 가 사용되는 경우를 살펴보자.
C++98/03에서는 불가능 했던
foo({1, 2, 3, 4, 5}); 가 지원되는 점이 눈여겨 볼 점이다.
생성자에 사용되는 initializer_list 를 살펴보자.
일반적인 생성자 보다 initializer_list<> 가 우선시 되어 호출된다.
2015년 9월 8일 화요일
[C++11] Uniform initializer
Uniform initializer
: C++11 에서는 {} 을 사용하여 모든 타입의 데이터 초기화를 할 수 있다.
또한 기존 C++에서 narrow down 이 허용되었던 형변환은
{} 구문을 사용할 경우 불가능하도록 됐다.
심지어 데이터 표현 범위(byte)를 넘어설 경우도 error 를 발생시킨다.
e.g, char c{300};
type(1 byte)에서 표현가능한 데이터 범위를 넘어설 경우
: C++11 에서는 {} 을 사용하여 모든 타입의 데이터 초기화를 할 수 있다.
또한 기존 C++에서 narrow down 이 허용되었던 형변환은
{} 구문을 사용할 경우 불가능하도록 됐다.
심지어 데이터 표현 범위(byte)를 넘어설 경우도 error 를 발생시킨다.
e.g, char c{300};
type(1 byte)에서 표현가능한 데이터 범위를 넘어설 경우
[C++11] nullptr
nullptr 란?
C++11 에서 지원하는 null pointer 상수
먼저 pointer 가 초기화될 수 있는 정수 값을 살펴보자.
정수 0은 모든 타입의 포인터에 암시적 형변환을 통해서 초기화 값으로 사용될 수 있다.
하지만 그 이외의 정수는 암시적 형변환이 적용되지 않아 포인터 초기화에 사용될 수 없다.
그렇다면 0을 void* 로 형변환하여 NULL 로 정의해서 사용한다면 어떨까?
foo(NULL); 를 호출할 때는 문제가 없다.
또한 C언어에서는 void* 가 다른 타입의 포인터로 암시적 형변환이 가능하다.
하지만, C++에서는 void* 이 다른 타입의 포인터로 암시적 형변환이 허용되지 않는다.
따라서 C++에서 goo(NULL); 을 호출하면 error 가 발생한다.
NULL 은 void* 이지 char* 가 아니기 때문이다.
그렇다면 C++에서는 NULL 을 어떻게 정의하고 있을까?
결국엔 말그대로 포인터에 null pointer 를 전달하고 싶어서 nullptr 이 필요한 것이다.
nullptr 은 integer 0 이 아닌,
포인터 0이기 때문에 int type 에 대입하면 error 가 발생한다.
다만, bool type 에는 형변환이 가능하다.
그렇다면 nullptr 의 정확한 정체는 무엇일까?
nullptr 의 타입은 nullptr_t 이고,
nullptr_t 타입의 특징은 다음과 같다.
1. 모든 타입의 포인터로 암시적 변환 가능
2. bool로 암시적 변환 가능
3. int 로 변환되지 않으나, 하지만 reinterpre_cast를 통한 변환은 가능
nullptr 을 전달해야 하는 간단한 예제를 살펴보자.
C++11 에서 제공하는 perfect forwarding 기법을 통해서
foo(int*) 함수를 wrapping 하는 function 을 만들어 호출하는 경우이다.
여기서는 perfect forwarding 이 요점이 아니므로 이것에 대한 설명은 다음으로 하도록 하고..
wrapFunc 함수에서는 T type으로 인자를 전달받아
foo 함수에 인자를 그대로 전달하게 되는데,
foo(int*) 함수의 경우, 인자로 int*를 전달받기 때문에
wrapFunc(foo, 0); 을 호출할 경우 error 가 발생한다.
결국엔 0을 전달하려 할 경우, nullptr 을 전달해야 할 것이다.
nullptr 가 단순히 null pointer 상수로 기억하기 보다 어떤 필요성이 있는지 생각하고 사용해야 할 것이다.
Reference : http://en.cppreference.com/w/cpp/language/nullptr
C++11 에서 지원하는 null pointer 상수
먼저 pointer 가 초기화될 수 있는 정수 값을 살펴보자.
정수 0은 모든 타입의 포인터에 암시적 형변환을 통해서 초기화 값으로 사용될 수 있다.
하지만 그 이외의 정수는 암시적 형변환이 적용되지 않아 포인터 초기화에 사용될 수 없다.
그렇다면 0을 void* 로 형변환하여 NULL 로 정의해서 사용한다면 어떨까?
foo(NULL); 를 호출할 때는 문제가 없다.
또한 C언어에서는 void* 가 다른 타입의 포인터로 암시적 형변환이 가능하다.
하지만, C++에서는 void* 이 다른 타입의 포인터로 암시적 형변환이 허용되지 않는다.
따라서 C++에서 goo(NULL); 을 호출하면 error 가 발생한다.
NULL 은 void* 이지 char* 가 아니기 때문이다.
그렇다면 C++에서는 NULL 을 어떻게 정의하고 있을까?
결국엔 말그대로 포인터에 null pointer 를 전달하고 싶어서 nullptr 이 필요한 것이다.
nullptr 은 integer 0 이 아닌,
포인터 0이기 때문에 int type 에 대입하면 error 가 발생한다.
다만, bool type 에는 형변환이 가능하다.
그렇다면 nullptr 의 정확한 정체는 무엇일까?
nullptr 의 타입은 nullptr_t 이고,
nullptr_t 타입의 특징은 다음과 같다.
1. 모든 타입의 포인터로 암시적 변환 가능
2. bool로 암시적 변환 가능
3. int 로 변환되지 않으나, 하지만 reinterpre_cast를 통한 변환은 가능
nullptr 을 전달해야 하는 간단한 예제를 살펴보자.
C++11 에서 제공하는 perfect forwarding 기법을 통해서
foo(int*) 함수를 wrapping 하는 function 을 만들어 호출하는 경우이다.
여기서는 perfect forwarding 이 요점이 아니므로 이것에 대한 설명은 다음으로 하도록 하고..
wrapFunc 함수에서는 T type으로 인자를 전달받아
foo 함수에 인자를 그대로 전달하게 되는데,
foo(int*) 함수의 경우, 인자로 int*를 전달받기 때문에
wrapFunc(foo, 0); 을 호출할 경우 error 가 발생한다.
결국엔 0을 전달하려 할 경우, nullptr 을 전달해야 할 것이다.
nullptr 가 단순히 null pointer 상수로 기억하기 보다 어떤 필요성이 있는지 생각하고 사용해야 할 것이다.
Reference : http://en.cppreference.com/w/cpp/language/nullptr
피드 구독하기:
글 (Atom)
[C++] meta programing
재귀 호출에 관해 template meta programming 을 적용한 예제를 살펴보자. #include using namespace std; int fact(int n){ if(n factorial 연산을 하는 일반적인 재귀 호출 함...
-
Smart Pointer : 포인터처럼 동작하며 자동으로 메모리를 해제하고 안전하게 resource를 관리하도록 돕는 객체 포인터는 소멸자가 호출되지 않아 memory leak이 발생한다. Java, C#같은 VM이 있는 언어는 VM에서 G...
-
Template specialization : class template 을 arguments에 따라서 customizing 하는 기법 먼저 위 예제의 첫 번째 template 선언처럼 일반적인 template 선언은 primary temp...
-
우리는 함수의 포인터를 사용하여 함수를 또 다른 함수의 인자로 또는 반환 타입으로 사용할 수 있다. 포인터 변수 선언 방법은 어떻게 하는가? 타입 * 변수명; (통상 C++은 type에 *를 붙여쓰고, C개발자는 변수명에 붙여씀) 함수 포인...