Effective C++. 이 책은 C++을 공부하던 사람이라면 한번 쯤은 읽었을 고전 명작이다. STL의 원리와 철학에 대해서 자세히 설명하고 독자로 하여금 새로운 개발 철학을 익히게 해준다.
그러나 아래 이책은 아는 사람이 많지 않다.
Effective C++이 효율성에 대해서 눈 뜨게 해 주었다면, Modern C++ design은 몰라서 전전긍긍 했을 때의 내 답답함을 해소해 주었다. 그러나 이 책에서 설명하는 핵심적인 기술 몇 가지는 C++11에서 지원해주거나 더 확장해서 지원한다. 이제는 직접 구현할 필요는 없을 지라도 공부에는 큰 도움을 줄 것이다.
템플릿의 기본적인 문법은 단순하다. 아래 처럼 템플릿을 선언한 후 형이 지정되면, 그에 맞춰 템플릿이 자동으로 클래스로 변환된다.
위의 예는 문자열 src에서 알 수 없는 형 TYPE의 값을 추출하기 위한 템플릿 클래스다. 개념적으로 TYPE은 어떠한 변수형 이라도 상관 없다. 그러나 문자열에서 특정한 형으로의 변환이 모두 같을 수는 없다. 정수형과 부동 소수점 형만 보더라도 atoi, atof 처럼 호출해야 하는 API가 달라진다. 우선 기본 변수형만 고려하여 보자.
이런 식으로 원하는 템플릿 파라미터 형태에 관해서는 명시적으로 정의하는 것을 '템플릿 특수화(Template specialization)'라 한다.
하지만 무엇인가 답답하다. 기본형이 int, short, long, unsigned int 등 모든 경우에 위와 같이 정의해야 한다면, 비효율적으로 보인다.
반복적으로 쓰이는 클래스를 통합해서 하나로 줄일 수 없을까?
이제 클래스를 위와 같이 확장할 것이다. 그리고 특수화를 아래와 같이 변경한다.
이제 아래에서 위의 두가지 경우는 동일한 template이 사용될 것이다.
이런 문법을 '부분 특수화(Partial specialization)'이라 한다.
그런데 Integer 형 특수화의 경우 내부에 템플릿을 하나 더 정의하였다. TYPE형이 64bit인지 아닌지 구분해서 사용하기 위한 템플릿이다. 문자열에서 정수로의 변환이 64bit일 경우 다르기 때문이다. 부분 특수화를 사용했지만 템플릿 파라미터에 기본 파라미터를 미리 정의하였다. inner_convert를 사용할때, is_64bit 값은 신경쓰지 않아도 자동으로 계산하게 되어 있다.
그러면 type_parser의 2,3 번 파라미터도 동일하게 적용할 수 있지 않을까? 가능하다. 단, 몇가지 선 작업이 필요하다.
TYPE 형이 부동 소수점인지 정수형인지 미리 판단이 가능해야 한다. 어떻게 할까? 간단하다.
이제 템플릿 선언을 수정하면 다음과 같다.
템플릿의 사용은 훨씬 간편해 졌다. 그리고 특수화를 제외한 템플릿 선언을 제거하였다. 이렇게 되면 특수화에 해당하는 타입이 아니면 컴파일 에러가 발생한다. 특수화에 해당하는 int, long 등만 사용한다면 에러가 발생하지 않을 것이다.
위에 정의한 type_parser라는 템플릿 클래스를 함수로 만드는 것은 불가능할까?
함수 템플릿은 클래스 템플릿과 비교하여 한가지 장점이 있다. 함수 템플릿의 템플릿 파라미터는, 입력 받는 변수에 따라 TYPE을 자동으로 선택된다. 그러나 이런 특징은 템플릿 뿐만 아니라 함수 오버로딩에도 적용되는 규칙이다. 오버로딩과 함수 부분 특수화는 같이 사용될 경우 실제 호출 되어야 할 함수를 결정할 수 없다.
(사실, 이 설명은 정확하지 않다. 이문제를 이해하려면 '함수형 프로그래밍-Functional programming'를 알아야 한다. 그리고 나도 FP는 잘 모른다.... )
template<class TYPE>
class type_parser
{
protected:
TYPE m_instance;
public:
type_parser(const std::string& src){}
~type_parser(){}
TYPE& get(){ return m_instance; }
size_t size() const { return sizeof(m_instance); }
const type_info& type() const { return typeid(TYPE); }
};
template<>
class type_parser<int>
{
protected:
int m_instance;
public:
type_parser(const std::string& src)
{
m_instance = ::atoi(src.c_str());
}
~type_parser(){}
int& get(){ return m_instance; }
size_t size() const { return sizeof(m_instance); }
const type_info& type() const { return typeid(int); }
};
template<>
class type_parser<double>
{
protected:
double m_instance;
public:
type_parser(const std::string& src)
{
m_instance = ::atof(src.c_str());
}
~type_parser(){}
double& get(){ return m_instance; }
size_t size() const { return sizeof(m_instance); }
const type_info& type() const { return typeid(double); }
};
....
이런 식으로 원하는 템플릿 파라미터 형태에 관해서는 명시적으로 정의하는 것을 '템플릿 특수화(Template specialization)'라 한다.
type_parser<int> variable("1");
type_parser<double> variable2("2");
처럼 선언하면 템플릿 파라미터에 따라 미리 정의된 템플릿이 사용될 것이다.하지만 무엇인가 답답하다. 기본형이 int, short, long, unsigned int 등 모든 경우에 위와 같이 정의해야 한다면, 비효율적으로 보인다.
반복적으로 쓰이는 클래스를 통합해서 하나로 줄일 수 없을까?
template<class TYPE , bool is_float , bool is_integer > class type_parser { protected: TYPE m_instance; public: type_parser(const std::string& src){} ~type_parser(){} TYPE& get(){ return m_instance; } size_t size() const { return sizeof(m_instance); } const type_info& type() const { return typeid(TYPE); } };
이제 클래스를 위와 같이 확장할 것이다. 그리고 특수화를 아래와 같이 변경한다.
template<class TYPE>
class type_parser<TYPE, false, true>
{
protected:
template<class T, bool is_64bit= sizeof(T)==8> struct inner_convert
{
static T convert(const char* t);
};
template<class T> struct inner_convert<T, true>
{
static T convert(const char* t) {return (T)::atoi(t);}
};
template<class T> struct inner_convert<T, false>
{
static T convert(const char* t) {return (T)::_atoi64(t);}
};
protected:
TYPE m_instance;
public:
type_parser(const std::string& src)
{
m_instance = inner_convert<TYPE>::convert(src.c_str());
}
~type_parser(){}
int& get(){return m_instance;}
size_t size() const {return sizeof(m_instance);}
const type_info& type() const {return typeid(int);}
};
template<class TYPE>
class type_parser<TYPE, true, false>
{
protected:
TYPE m_instance;
public:
type_parser(const std::string& src)
{
m_instance = (TYPE)::atof(src.c_str());
}
~type_parser(){}
double& get(){ return m_instance; }
size_t size() const { return sizeof(m_instance); }
const type_info& type() const { return typeid(double); }
};
특수화를 2개로 줄였고, 각 특수화에 TYPE형은 아직도 결정안된 것으로 남겨 두었다.이제 아래에서 위의 두가지 경우는 동일한 template이 사용될 것이다.
type_parser<int, false, true> variable("1");
type_parser<long, false, true> variable2("2");
type_parser<double, true, false> variable3("3")
이런 문법을 '부분 특수화(Partial specialization)'이라 한다.
그런데 Integer 형 특수화의 경우 내부에 템플릿을 하나 더 정의하였다. TYPE형이 64bit인지 아닌지 구분해서 사용하기 위한 템플릿이다. 문자열에서 정수로의 변환이 64bit일 경우 다르기 때문이다. 부분 특수화를 사용했지만 템플릿 파라미터에 기본 파라미터를 미리 정의하였다. inner_convert를 사용할때, is_64bit 값은 신경쓰지 않아도 자동으로 계산하게 되어 있다.
그러면 type_parser의 2,3 번 파라미터도 동일하게 적용할 수 있지 않을까? 가능하다. 단, 몇가지 선 작업이 필요하다.
TYPE 형이 부동 소수점인지 정수형인지 미리 판단이 가능해야 한다. 어떻게 할까? 간단하다.
template<class TYPE> struct is_float{ enum {value = 0}; };
template<> struct is_float<float>{ enum{value=1};};
template<> struct is_float<double>{ enum{value=1};};
template<class TYPE> struct is_integer{ enum {value = 0}; };
template<> struct is_integer<int>{ enum{value=1};};
template<> struct is_integer<long>{ enum{value=1};};
...
위와 같은 템플릿들을 미리 정의하면 된다. 템플릿을 특수화하여 원하는 경우에는 value 값이 1이 되도록 하면 된다. 위의 템플릿들은 std::is_floating_point, std::is_integral 이라는 이름으로 이미 선언되어 있다.이제 템플릿 선언을 수정하면 다음과 같다.
template<class TYPE , bool is_float = std::is_floating_point<TYPE>::value , bool is_integer = std::is_integral<TYPE>::value > class type_parser; template<class TYPE> class type_parser<TYPE, false, true> { protected: template<class T, bool is_64bit= sizeof(T)==8> struct inner_convert { static T convert(const char* t); }; template<class T> struct inner_convert<T, true> { static T convert(const char* t) {return (T)::atoi(t);} }; template<class T> struct inner_convert<T, false> { static T convert(const char* t) {return (T)::_atoi64(t);} }; protected: TYPE m_instance; public: type_parser(const std::string& src) { m_instance = inner_convert<TYPE>::convert(src.c_str()); } ~type_parser(){} int& get(){return m_instance;} size_t size() const {return sizeof(m_instance);} const type_info& type() const {return typeid(int);} }; template<class TYPE> class type_parser<TYPE, true, false> { protected: TYPE m_instance; public: type_parser(const std::string& src) { m_instance = (TYPE)::atof(src.c_str()); } ~type_parser(){} double& get(){return m_instance;} size_t size() const {return sizeof(m_instance);} const type_info& type() const {return typeid(double);} };
type_parser<int> variable("1");
type_parser<long> variable2("2");
type_parser<double> variable3("3");
템플릿의 사용은 훨씬 간편해 졌다. 그리고 특수화를 제외한 템플릿 선언을 제거하였다. 이렇게 되면 특수화에 해당하는 타입이 아니면 컴파일 에러가 발생한다. 특수화에 해당하는 int, long 등만 사용한다면 에러가 발생하지 않을 것이다.
위에 정의한 type_parser라는 템플릿 클래스를 함수로 만드는 것은 불가능할까?
template<class TYPE>
inline TYPE type_parse(const std::string& src)
{
}
위 처럼 사용해도 괜찮다. 단, 함수 템플릿은 부분 특수화를 허용하지 않는다. 그렇기에 위에서 사용하는 bool 템플릿 파라미터를 응용한 기법을 사용할 수 없다. 함수로 템플릿을 정의하려면, 사용하고자 하는 모든 TYPE에 대하여 특수화를 선언하면 된다.함수 템플릿은 클래스 템플릿과 비교하여 한가지 장점이 있다. 함수 템플릿의 템플릿 파라미터는, 입력 받는 변수에 따라 TYPE을 자동으로 선택된다. 그러나 이런 특징은 템플릿 뿐만 아니라 함수 오버로딩에도 적용되는 규칙이다. 오버로딩과 함수 부분 특수화는 같이 사용될 경우 실제 호출 되어야 할 함수를 결정할 수 없다.
(사실, 이 설명은 정확하지 않다. 이문제를 이해하려면 '함수형 프로그래밍-Functional programming'를 알아야 한다. 그리고 나도 FP는 잘 모른다.... )
댓글 없음:
댓글 쓰기