제너릭 프로그래밍이란?

Report 2010. 2. 2. 18:07 Posted by zetz

INTRODUCTION

제너릭 프로그래밍은 소프트웨어 콤포넌트를 일반화시켜서 상황에 따른 다양한 변화에 재사용이 용이해졌다.
C++, 클래스와 함수 템플릿은 제너릭 프로그래밍을 위한 특히 효과적인 기법이다. 왜냐하면 그것은 효율성을 희생하지 않고 일반화가 가능하기 때문이다.
제너릭 프로그래밍의 한 예로서, 우리는 C 표준화 라이브러리의 memcpy() 함수의 하나의 일반화 가능성을 볼 것이다.

void* memcpy(void* region1, const void* region2, size_t n)

{

const char* first = (const char*)region2;

const char* last = ((const char*)region2) + n;

char* result = (char*)region1;

while (first != last)

*result++ = *first++;

return result;

}

 

memcpy() 함수는 void*의 사용에 의한 약간의 확장으로 이미 일반화되었다. 이 함수는 다른 종류의 데이터 배열을 복사하는 것이 가능하다. 그러나 그 데이터가 링크드-리스트와 같은 연속적인 데이터가 아니라면 어떻게 되는가? 우리는 복사의 개념을 연속적인 요소들의 시퀀스로 정의해야 되는가? memcpy() 함수 본체를 보자. 이 함수의 최소 필수조건은 포인터 정렬을 통한 시퀀스의 순회, 포인터로 가리켜진 요소에의 접근 및 목적지 요소의 쓰기, 멈추기 위한 비교가 필요하다. C++ 표준 라이브러리 그룹 요구명세서는 이러한 컨셉에 대한 Input Iterator Concept 과 Output Iterator concept 의 경우를 예로 든다.

만약 우리가 템플릿 기능과 템플릿 파라메터 요구명세서에 설명된 Input Iterator, Output Iterator 을 사용하여 memcpy() 를 재작성한다면, 우리는 다음과 같이 재사용성이 좋은 copy() 를 구현할 수 있다.

 

template <typename InputIterator, typename OutputIterator>

OutputIterator

copy(InputIterator first, InputIterator last, OutputIterator result)

{

while (first != last)

*result++ = *first++;

return result;

}

 

제너릭 copy() 함수를 사용하여, 우리는 STL의 링크드-리스트를 포함한 어떠한 종류의 시퀀스 요소들도 복사할 수 있다.

#include <list>

#include <vector>

#include <iostream>

 

int main()

{

const int N = 3;

std::vector<int> region1(N);

std::list<int> region2;

 

region2.push_back(1);

region2.push_back(0);

region2.push_back(3);

 

std::copy(region2.begin(), region2.end(), region1.begin());

 

for (int i = 0; i < N; ++i)

std::cout << region1[i] << " ";

std::cout << std::endl;

}

 

ANATOMY OF A CONCEPT

컨셉은 유효한 표현들, 연관된 타입, 불변성, 복잡성 보장과 같은 요구사항들로 구성된 하나의 집합이다. 그 요구사항들을 만족하는 한 타입을 모델 컨셉이라 부른다. 그 컨셉은 요구명세에 있는 다른 컨셉으로 확장할 수 있고, 이를 개선(refinement)이라 한다.

  • Valid Expressions(유효한 표현)
    • 컨셉 모델을 고려한 표현이 포함된 오브젝트의 성공적인 컴파일을 위한 C++ 표현들
  • Associated Types(연관 타입)
    • 하나 또는 그 이상의 다양한 표현들이 참여된 모델링 타입에 관계된 타입
    • 전통적으로 연관 타입은 모델링 타입의 클래스 정의 내부의 typedefs nested 를 통하거나 traits 클래스를 통해서 접근된다.
  • Invariants(불변성)
    • 오브젝트의 항상 참인 런-타임 특징. 즉 , 오브젝트를 포함한 함수들은 그들의 특징들이 반드시 보존된다.
    • 불변성들은 대부분 선-조건과 후-조건의 모습으로 나타난다.
  • Complexity Guarantees(복잡성 보장)
    • 다양한 표현들의 하나의 실행을 얼마나 길게 취할 수 있는지 또는 다양한 자원이 얼마나 많이 그들의 계산에 사용될 수 있는지 최대 한계점이다.


C++ 표준 라이브러리에 사용된 컨셉들은 SGI STL site< http://www.sgi.com/tech/stl/table_of_contents.html> 에 문서화 되어있다.

TRAITS

특성 클래스는 정보를 컴파일-타임 요소 (타입, 필수적인 상수 또는 주소)와 연관하는 한 방법을 제공한다.
예를 들어 클래스 템플릿 std::iterator_traits<T> 은 아래와 같이 볼 수 있다.

template <class Iterator>

struct iterator_traits {

typedef ... iterator_category;

typedef ... value_type;

typedef ... difference_type;

typedef ... pointer;

typedef ... reference;

};

 

특성의 value_type 은 이터레이터는 "pointing it" 란 타입을 제너릭하게 코드화 하게 해주었다. 반면, iterator_category 이터레이터의 능력들에 의존한 더욱 효과적인 알고리즘의 선택을 위해 사용된다.

특성 템플릿 하나의 중요한 기능은 거슬리지 않는 것이다. 그것은 우리에게 임의적인 타입들, 빌트-인 타입과 제3의 라이브러리에 정의된 타입들과 함께 정보에 접근하는 것을 허용하였다. 일반적으로 특성들은 부분적으로 전공하는 특징 템플릿에 의한 한 특정한 타입을 위해 명시된다.

SGI가 제공하는 std::iterator_traits 의 설명을 자세히 보면, <see this page> 또 하나 매우 다른 특징언어의 표현은 std::numeric_limits<T> 이다. 제공된 끊임없이 변함없이 숫자 타입의 범위와 수용력 설명하고 있다.

 

TAG DISPATCHING
(전산에서 디스패치란 컴퓨터 처리결과를 데이터 처리 의뢰처에 배포하는 것)

태그 디스패칭은 타입의 소유물에 기반한 디스패치을 위한 함수 오버로딩을 사용하는 한 방법이다 . 그리고 대부분 특성 클래스들과 밀접히 사용된다. 이런 시너지효과 의 좋은 예가 C++ 표준 라이브러리의 std::advance() 함수이다. 이터레이터의 n번의 증가 같은 것들
이터레이터의 종류에 따라 그것들은 구현상 적용할 수 있는 최적화의 차이가 난다. 만약 그 이터레이터가 임의접근(전방, 후방으로 임의거리)이라면, advance() 함수는 i += n 과 같이 단순히 구현된다. 그리고 상수타임으로 매우 효율적이다. 다른 이터레이터는 반드시 단계별로 진전되며, 선형 명령어를 만들어낸다. 만약 이터레이터가 양방향이라면, 음수부터 N까지란 의미가 되며, 그래서 반드시 이터레이터가 증가인지 감소인지 결정해야한다.

태그 디스패칭과 traits classes 사이의 관계는 다음과 같다. 디스패칭을 위해 사용된 속성은 대부분 특성 클래스를 통해서 접근한다. 한 중요 advance() 함수는 iterator_category를 얻기 위해 iterator_traits 클래스를 사용한다. 이것은 advance_dispath() 함수 오버로딩을 의미한다. 한 적절한 advance_dispath() 는 모든 종류의 Input_iterator_tag, bidirectional_iterator_tag, random_access_iterator_tag 중 어떤 하나에 의해 해결되는 iterator_category 에 기반한 컴파일러에 의해 선택된다.