2016년 10월 2일 일요일

템플릿 함수에 배열의 크기를 전달하는 방법

우리 회사는 코드 리뷰 결과를 주기적으로 회신하여 코드의 품질을 제고하는 활동을 진행한다. 코드 리뷰어가 점검 결과를 전달하면 구현부서에서 이를 다시 검토하여 보완 여부를 결정하는 식이다.

한 번은 아래와 같은 템플릿 함수에 대하여 매개변수 T1, T2의 범위 체크(range check)를 요청받았다.
(코드는 샘플코드로 대체한다.)

template <typename T1, typename T2>  
 void update(unsigned short idx, T1 arr1[], T2, arr2[])     
 {                               
   if(idx == FA_1)  
   {                             
     arr1[3] = arr2[3];  // arr1과 arr2 배열의 범위를 알 수 없으므로 문제가 있는 코드입니다.
   }                             
   else if(idx == FA_2)                     
   {  
     arr1[2] = arr2[2];                   
   }                             
   else   
   {                             
     arr1[0] = arr2[0];                   
   }  
 }    
아마도 이 코드를 작성한 개발자는 매개변수의 타입 및 배열의 크기를 일반화 하려는 목적으로 함수 템플릿으로 기능을 구현한 것으로 추정할 수 있다.C/C++ 개발자들은 함수의 매개변수로 배열을 전달할 때 포인터 변수를 사용하기도 하는데, 이는 전달 받는 함수에서는 배열의 크기를 잃게 되므로 구현시 주의해야 한다.
(이러한 현상을 타입 디케이-Type Decay-라고 한다.)

이왕 배열과 포인터 얘기가 나옴 김에 다소 원론적인 질문을 던져보기로 한다.
C++ 에서 배열과 포인터는 어떻게 다른가 ?

첫째, 포인터는 변수이고 배열 상수이다. 쉽게 말해 아래와 같은 코드는 허용하지 않는다.

   int* ptr = NULL;   // ptr은 Pointer type
   int arr1[10] = { 0, };  // arr1, arr2는 Array type 
   int arr2[10] = { 0, };   
   
   ptr  = arr1;  // OK, Pointer는 변수
   arr1 = arr2;  // NOK, arr1, arr2는 상수이다.
둘째, 포인터가 가리키는 메모리의 크기는 동적으로 결정할 수 있지만, 배열이 가리키는 메모리의 크기는 선언할 때 정적으로 결정된다.

셋째, 배열을 함수의 매개변수로 전달할 때 배열 타입이 아닌 포인을 사용할 경우 배열의 크기는 전달할 수 없다. 포인터 타입으로 매개변수를 전달할 경우 배열의 크기 정보를 잃게 된다(아까 말했던 타입 디케이).
아래의 코드를 보자

 void funcP(int* arr)  
 {  
   printf("sizeof arr(%zu)\n", sizeof(arr));  // 결과는 '4'아니면 '8'
 }

 int main(void)  
 {  
   int arr1[10] = { 0, };  
   
   printf("sizeof arr(%zu)\n", sizeof(arr1)); // 결과는 '10'
   funcP(arr1);  
   
   return 0;  
 }  
C/C++에서 배열의 정의는 다음과 같다.
http://www.cplusplus.com/doc/tutorial/arrays/


즉, 배열 변수를 선언하는 순간 해당 변수는 '타입'과 '크기'정보를 포함하게 된다.

이제 마지막으로 다시 처음의 코드로 돌아와서 코드 리뷰어의 요청사항을 어떻게 반영했는지 살펴보자.

 template <typename T1, typename T2, size_t N, size_t M>  
 void update(unsigned short idx, T1 (&arr1)[N], T2 (&arr2)[M])  
 // 배열 매개변수를 레퍼런스로 전달 받았다. 
 // 원래의 코드에서는 포인터로 전달 받았기 때문에 크기를 알 수가 없었음.
 // 이제 타입 디케이 현상 없음.
 {  
   if(idx == FA_1)  
   {    
     if(3 >= N || 3 >= M) return;  // 변수 범위를 체크합니다.
   
     arr1[3] = arr2[3];  
   }    
   else if(idx == FA_2)  
   {    
     if(2 >= N || 2 >= M) return;  // 변수 범위를 체크합니다.
   
     arr1[2] = arr2[2];  
   }    
   else   
   {    
     if(1 >= N || 1 >= M) return;  // 변수 범위를 체크합니다.
   
     arr1[0] = arr2[0];  
   }  
 }  
위 코드는 매개변수로 전달하는 배열의 크기가 다양할 경우 '코드 부풀림(Code Bloating)' 현상이 발생하지 않도록 주의해야 한다. 실제 코드에서는 한정된 크기의 배열에 한하여 해당 함수를 호출했기 때문에 문제없이 사용할 수 있었다.

댓글 없음:

댓글 쓰기