레이블이 개발인 게시물을 표시합니다. 모든 게시물 표시
레이블이 개발인 게시물을 표시합니다. 모든 게시물 표시

2015년 6월 17일 수요일

C/C++ , 좋은 프로그래밍 - 템플릿 - 4

이제 템플릿 이야기의 마지막으로 템플릿 리스트 기법과 C++11의 Variadic template을 잠시 이야기 하겠다. 사실 템플릿 리스트 기법과 Variadic template이 필요한 거의 모든 경우는 이미 구현되어 있다. 그래서 지금 이야기 하는 것을 굳이 이해할 필요는 없고, 이미 구현된 것들의 사용방법만 숙지하여도 충분하다.

첫번째로 템플릿 리스트를 이야기 해보자.
일반적으로 리스트는 이미 만들어진 데이터를 보관하기 위한 용도로 사용된다. 템플릿 리스트는 비슷한 의미로 객체 형을 보관할 수 있다. 앞서 이야기한 function, bind는 C++11이전에는 템플릿 리스트를 사용하여 구현한다.

template <class T, class U>
struct type_list
{
 typedef T head;
 typedef U tail;
};

struct null_type
{
};
단순한 템플릿이다. 두 가지 타입을 입력 받아 각각의 타입을 head, tail이라는 이름으로 재선언 한다. null_type은 아무 것도 없는 빈 클래스다. 리스트의 끝을 표현하기 위해 사용했다.

typedef type_list<int, type_list<short, type_list<double, null_type>>> type_list_sample;

위의 예시처럼, type_list를 재귀적으로 사용 가능하다. 위의 형은 컴파일 시점에 해석되어지기 때문에 런타임에는 아무런 영향도 미치지 않는다. 형은 형일 뿐이다. 그럼 위의 type_list_sample 따위의 형은 어떻게 사용할까?

template<class TLIST> struct type_group;

template<class T>
struct type_group<type_list<T, null_type>>
{
 T value;
};

template<class T, class TLIST>
struct type_group<type_list<T, TLIST>> : public type_group<TLIST>
{
 T value;
};

type_group<type_list_sample> instance;
위 구문은 type_list_sample의 실제 객체를 만드는 예제다. 템플릿의 재귀적인 전개를 이용하여 반복적으로 상속하고 최종적으로 원하는 객체를 만들어 냈다.
그런데 객체에 값을 대입하거나, 객체의 값을 읽는 행위는 어떻게 할까? 그것을 위해 몇가지 템플릿을 추가로 정의한다.

template <class TYPE_LIST, class T>
struct index_of;

template <class T>
struct index_of<null_type, T> 
{
     enum {index = -1};
};

template <class T, class TAIL>
struct index_of<type_list<T, TAIL>, T>
{
   enum {index=0};
};

template <class HEAD, class TAIL, class T>
struct index_of<type_list<HEAD, TAIL>, T> 
{
private: enum {temp = index_of<TAIL, T>::index};
public: enum {index = temp == -1? -1:1+temp};
};
조금은 복잡하지만 끊기있게 해석하기 바란다.
index_of의 일반형을 선언했지만, 정의는 없다. 아래의 명시적으로 정의된 형이 모든 형을 커버하기 때문에 굳이 구체적으로 정의할 필요는 없다.
index_of 템플릿은 아래 처럼 사용한다.

const int index_position = index_of<type_list_sample, int> ::index;

위의 예제로 type_list의 활용 방법을 보자. type_list_sample은 앞서 정의한 것 처럼, int, short, double을 그 멤버로 가지는 리스트다.
index_of의 템플릿 파라미터가 type_list와 int이기 때문에 3개의 명시적 정의 중, 2번째가 선택된다.

type_list_sample가 type_list<int, type_list<short, type_list<double, null_type>>> 임으로 index_of<type_list_sample, int> 형태는 아래 2개와 비교하면, 명확할 것이다.

struct index_of<null_type, T>
struct index_of<type_list<T, TAIL>, T>
struct index_of<type_list<HEAD, TAIL>, T> 
그러므로 index_position은 0이다.

index_of<type_list_sample, char> ::index 은 어떨까? 
1. 첫 번째, 두 번째는 만족하지 않기 때문에 자동적으로 세 번째가 선택될 것이다. 
 HEAD = int
 TAIL = type_list<short, type_list<double, null_type>>
 T = char

2. index_of<type_list<short, type_list<double, null_type>, char>::index 역시 세 번째다
  HEAD = short
  TAIL = type_list<double, null_type>
  T = char

3. index_of<type_list<double, null_type>, char>::index는 세 번째다

4. index_of<null_type, char>은 첫 번째다.

결국 index_position은 -1이된다.

한번 더 해보자
const int index_position = index_of<type_list_sample, double> ::index;
은 위의 3번이 두 번째가 선택된다.
index_of<type_list<double, null_type>, double>::index
그러므로 index_position = 2이다.


비슷하게 다음 템플릿도 정의할 수 있다.


template <class TYPELIST, int index>
struct type_at;

template <class HEADER, class TAIL>
struct type_at<type_list<H, T>, 0> 
{
 typedef H type;
};

template <class HEADER, class TAIL, int i> 
struct type_at<type_list<HEADER, TAIL>, i>
{
 typedef typename type_at<TAIL, i-1>::type type;
};
사용 예는 다음과 같다.


typedef type_at<type_list_sample, 2>::type second_type;



간단한 사용 예제를 보자.


template<class TLIST> struct type_group;

template<class T>
struct type_group<type_list<T, null_type>>
{
 T value;

 template<int N> void* base_ptr()
 {
  return (void*)&value;
 }
 template<int N> T& get()
 {
  return value;
 }
};

template<class T, class TLIST> 
struct type_group<type_list<T, TLIST>> : public type_group<TLIST>
{
 T value;

 template<int N> void* base_ptr()
 {
  return type_group<TLIST>::base_ptr<N-1>();
 }
 template<> void* base_ptr<0>()
 {
  return (void*)&value;
 }
 template<int N> inline typename type_at<type_list<T, TLIST>, N>::type& get()
 {
  unsigned char* p = base_ptr<N>();
  return *(type_at<type_list<T, TLIST>, N>::type*)p;
 }
};

int _tmain(int argc, _TCHAR* argv[])
{
 
 type_group<type_list<int, type_list<short, type_list<double, null_type>>>> group_instance;
 group_instance.get<0>() = 1;
 group_instance.get<1>() = 2;
 group_instance.get<2>() = 100.0;
}

위의 예제는 std::tuple을 흉내내어, 심플하게 구현한 것이다. 

이런 type_list는, Variadic template이 없다면 앞서 이야기한 std::function이나 std::bind 등을 구현하기 위해서 필수적으로 사용되어야 한다.

두번째로, Variadic template을 이야기하자.
Variadic template은 템플릿 파라미터 숫자를 정하지 않은 상태로 받아 들이 수 있다. type_list의 확장 버전인 셈이다. 여기서는 소개하지 않겠지만, C++11의 Rvalue-reference와 결합하면 Perfect-forwarding이 가능해져 더욱 강력하다.

위의 예제들을 그대로 만들어 보겠다.

template<class ...> struct type_check;

template <class T, class TLIST> struct index_of2
{
 enum{ value = -1 };
};
template <class T, class ...TLIST> struct index_of2<T, type_check<T, TLIST...>>
{
 enum { value = 0 }; 
};
template <class T, class U, class ...TLIST> struct index_of2<T, type_check<U, TLIST...>> 
{
 enum { value = index_of2<U, type_check<TLIST...>>::value == -1 ? -1 : 1 + index_of2<U, type_check<TLIST...>>::value };
 
};

template <class type_check, int N> struct type_at2;

template <class T, class ...TLIST>
struct type_at2<type_check<T, TLIST...>, 0>
{
 typedef T type;
};

template <class T, class ...TLIST, int N>
struct type_at2<type_check<T, TLIST...>, N>
{
 typedef typename type_at2<type_check<TLIST...>, N-1>::type type;
};

type_check만 제대로 이해하면 type_list와 똑같다.
type_check은 N개의 임의의 템플릿 파라미터에서 가장 앞의 템플릿 파라미터를 분리 할 수 있게 해준다. 즉, type_check<...>와 type_check<T, ...>은 같은 형이기 때문에 T에 대하여 원하는 검사를 수행 할 수 있다.

사용 방법은 다음과 같다.

template<class ...T>
struct test
{
 const int n = index_of2<int, type_check<T...>>::value;
 const int total_size = sizeof...(T);
 typedef typename type_at2<type_check<T...>, 2>::type type;
};


int _tmain(int argc, _TCHAR* argv[])
{
 test<int, short, long> t;
 test<int, short, long>::type d = 100.0;
 printf("%d, %d", t.n, t.total_size);


 return 0;
}

이해하지 않아도 괜찮다. 앞서 언급했지만 응용해서 만들 수 있는 거의 대부분이 이미 STL에 구현되어 있다. 사용 방법만 익히면 된다. type_check은 std::tuple 로 이미 선언되어 있고, type_at2는 std::tuple_element로 구현되어 있다. 다만 왠 일인지 index_of2는 구현되어 있지 않다.  (혹은 내가 모르는 것일 수도 있다.)
sizeof...(T) 는 전체 템플릿 파라미터의 갯수를 구할 수 있는 신규 문법이다.

Variadic template은 vs2013이후 기본 지원하나, vs2012는 http://www.microsoft.com/en-us/download/details.aspx?id=35515 에서 다운 받은 후 추가 설치해야 하고  vs2010 이하는 지원하지 않는다.




C/C++ , 좋은 프로그래밍 - 템플릿 - 3


이제 분기문에 대해서 이야기 해보자.
네트워크 프로그램을 제작한다고 상상해보자. 보통의 처리 방법은 모든 패킷에 번호를 부여하여 그 번호에 따라 실행할 구문을 정한다.


switch(packet_type)
{
case PACKET_TYPE_1:
 ...
 break;

case PACKET_TYPE_2:
 ...
 break;
}
따위의 구문이 일반적이다. 그러나, 패킷 종류가 수십 개를 넘어 간다면 위의 형태 처럼 프로그램을 만드는 것은 바람직해 보이지 않는다.

여기서는 2가지 방법을 소개해 보려고 한다.
첫 번째는 함수 포인터를 이용하는 방법이다. 엄밀하게 사용하여야 하지만, 직관적이고 편리하다.
두 번째는 템플릿를 이용하는 방법이다. 첫번째에 비하여 훨씬 유연하지만, 잘못 제작할 경우 런타임 비용이 클 수 있다.

두가지 방법 모두 일단 STL의 map을 활용하자. map 대신, 배열을 사용할 수도 있지만 유연성을 위해서 map을 사용하겠다.

함수 포인터를 이용하는 방법은 아래와 같은 형태를 취한다.
class packet_class
{
};

void packet_function_1(packet_class* packet)
{
}
void packet_function_2(packet_class* packet)
{
}

int _tmain(int argc, _TCHAR* argv[])
{
	typedef void (*FUNCTION)(packet_class* packet);
	std::map<int, FUNCTION> function_map;

	function_map[1] = &packet_function_1;
	function_map[2] = &packet_function_2;

	packet_class* packet;
	function_map[0](packet);
}
이해하기 어려운 내용은 없다.
단지 불편한 점이 있다면, 전역 함수를 사용하기 때문에 객체 지향 위주의 프로그램에서는 객체를 넘기는 방법을 마련해야 한다는 것이 그거다.
그러면 함수 포인터를 멤버 함수 포인터로 바꾸자.

class packet_class
{
};

template<class TYPE>
class dispatch_class
{
public:
	typedef void (TYPE::*DISPATCH_FUNCTION)(packet_class* packet);

protected:
	TYPE* m_dispatch_instance;
	std::map<int, DISPATCH_FUNCTION> m_functions;

public:
	dispatch_class(TYPE* dispatch_instance) : m_dispatch_instance(dispatch_instance) {}
	void set_dispatch(int packet_type, DISPATCH_FUNCTION f)
	{
		m_functions[packet_type] = f;
	}

	void packet_dispatch(int packet_type, packet_class* packet)
	{
		DISPATCH_FUNCTION f = m_functions[packet_type];
		if (f != NULL)
		{
			(m_dispatch_instance->*f)(packet);
		}
	}
};

class dispatcher
{
public:
	void packet_function_1(packet_class* packet)
	{
	}
	void packet_function_2(packet_class* packet)
	{
	}

public:
	dispatcher()
	{
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	dispatcher packet_dispatcher;
	dispatch_class<dispatcher> dispatcher_executor(&packet_dispatcher);
	dispatcher_executor.set_dispatch(0, &dispatcher::packet_function_1);
	dispatcher_executor.set_dispatch(2, &dispatcher::packet_function_2);

	
	packet_class* packet;
	dispatcher_executor.packet_dispatch(0, packet);
}

앞서 사용한 함수 포인터와 멤버 함수 포인터의 차이점만 알고 있다면 크게 어려운 내용은 아닐 것이다.
멤버 함수 포인터 변수를 선언하는 방법과 그 변수형으로 저장된 포인터, 클래스 객체를 이용하여 멤버 함수를 호출하는 방법만 눈여겨 보기 바란다. 이 내용은 이해하기 보단 그냥 그대로 외워야 한다.

이제 템플릿을 이용해 보자. 그리고 펑크터도 이용할 것이다.
[C/C++ , 좋은 프로그래밍 - 재사용 : 템플릿 - 1]에서 미리 언급하였지만, 펑크터는 클래스 객체의 operator()(type)를 활용하여 객체를 마치 전역 함수처럼 호출하는 방법이다.
사실 멤버 함수 호출하는 방법을 굳이 operator()(type)을 쓰는 것이 꼭 필요하겠냐는 의문이 생긴다.
그러나 아래 예제를 보면 조금 이해가 쉬울 것이다. 펑크터의 장점은 함수 포인터와 여러 클래스 사이의 형의 차이를 알 필요가 없다는 점이다.


void static_function()
{
	printf("11");
}

struct functor
{
	void operator()()
	{
		printf("22");
	}
};

template<class F> 
void caller(F f)
{
	f();
}

int _tmain(int argc, _TCHAR* argv[])
{
	caller(&static_function); //case 1

	functor f;  //case 2
	caller(f);
}
즉, 값을 받아 들이는 입장에서는 객체의 특정 멤버 함수를 알 필요도 없고, 값이 객체인지 함수 포인터인지 알 필요가 없다. 주의 할 점이 이 이야기는 런타임에 관련된 이야기가 아니다. 컴파일 중, caller가 f를 어떻게 호출해야 할지 자동으로 결정한다는 이야기이다.

이제 앞선 dispatch_class를 약간 수정하자.

template<class F>
class dispatch_class
{
protected:
	std::map<int, F> m_functions;

public:
	dispatch_class(){}
	void set_dispatch(int packet_type, F f)
	{
		m_functions[packet_type] = f;
	}

	void packet_dispatch(int packet_type, packet_class* packet)
	{
		auto p = m_functions.find(packet_type);
		if (p != m_functions.end())
		{
			(p->second)(packet);
		}
	}
};

그럴 듯 보이는가? 사실 이 클래스를 사용하기에는 큰 문제점이 있다. class F가 클래스의 템플릿 파라미터라는 것이 문제다. 위 모양을 보면 템플릿 클래스가 컴파일러에 의해서 전개되기 전에 F 형이 반드시 결정되어 있어야 한다. 패킷 디스패치 기능을 만들어 내기 위해서는 다양한 함수를 등록할 수 있어야 하지만 이 템플릿은 클래스 하나당 함수 하나를 등록할 수 있기 때문에 목적에 맞지 않다. 방법이 없을까? 아니다. 이제 가장 중요한 템플릿 3개를 소개하겠다.

std::function (vs2008일 경우, std::tr1::function)
std::bind (vs2008일 경우, std::tr1::bind)
std::placeholders::_1, _2, ... (vs2008일 경우 std::tr1::placeholders::_1, _2,....)

function은 모든 호출 가능한 형을 대신할 수 있다.
bind는 모든 호출 가능한 형과 그 파라미터를 조합할 수 있다.
placeholders는 모든 알려지지 않은 호출 파라미터를 대표할 수 있다.

위에 소개한 템플릿의 유용성은 이루 말할 수 없을 정도이다. 꼭 사용법을 숙지해두길 권장한다.
위의 템플릿을 실제로 구현하기 위해서는 템플릿 리스트 기법을 사용하던가 아니면 c++11의 Variadic template을 사용해야 한다. 여기서는 템플릿의 사용 방법을 이야기 하고, 추후 간단히 구현 원리를 소개하겠다.



이 3가지 조합으로 모든 호출을 단일 펑크터로 만들 수 있다. 다시 dispatch_class를 수정해 보자.

class dispatch_class2
{
protected:
	std::map<int, std::function<void(packet_class*)>> m_functions;

public:
	dispatch_class2(){}
	void set_dispatch(int packet_type, std::function<void(packet_class*)> f)
	{
		m_functions[packet_type] = f;
	}

	void packet_dispatch(int packet_type, packet_class* packet)
	{
		std::function<void(packet_class*)> f = m_functions[packet_type];
		if (f)
		{
			f(packet);
		}
	}
};

이 클래스와 앞서 소개했던 함수 포인터 형태와의 차이점을 이해 하겠는가?
일견 비슷해 보이지만, 함수 포인터 형태는 그 형이 정해져 있다. 전역 함수이던, 멤버 함수이던 호출 파라미터를 비롯한 형에 관련된 정보를 개발자가 미리 맞춰 주어야 한다. function 사용 클래스는 유연하다. 다음 예제를 보자

class dispatch_test
{
public:
	void packet_1(packet_class* packet)
	{
	}
	void packet_2(packet_class* packet)
	{
	}
};

void d1(packet_class* packet)
{
}

void d2(dispatch_test* dt, packet_class* packet)
{
}

struct my_param {};

class dispatch_test2
{
public:
	void packet_1(my_param* p, packet_class* packet)
	{
	}
	void packet_2(packet_class* packet, int p2)
	{
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	dispatch_test instance;
	dispatch_test2 instance2;
	my_param mp;

	dispatch_class2 test2;
	test2.set_dispatch(0, std::bind(&dispatch_test2::packet_1, &instance2, &mp, std::placeholders::_1));
	// instance2의 멤버 함수를 등록하고, 추가로 mp의 포인터가 첫번째 파라미터로 전달되게 한다. 
	// dispatch_class에서 입력하는 packet_class* 는 두번째 파라미터로 전달한다.

	test2.set_dispatch(1, std::bind(&dispatch_test2::packet_2, &instance2, std::placeholders::_1, 100));
	//instance2의 멤버 함수를 등록하고, 추가로 두번째 파라미터로 int를 전달한다.
	
	test2.set_dispatch(3, std::bind(d1, std::placeholders::_1));
	//전역함수 d1을 등록한다.
	
	test2.set_dispatch(4, std::bind(d2, &instance, std::placeholders::_1));
	//전역 함수 d2를 등록하고 첫번째 파라미터로 instance를 사용하도록 한다.
	
	return 0;
}
dispatch_class와 비교하여 dispatch_class2는 함수 형에 거의 제한이 없다. bind 함수의 인자들은 bind 함수로 전달 되는 인자들은 bind로 만들어지는 클래스의 멤버로 어딘가에 저장되어 있다. 어딘가에 저장된 멤버들이 실제 함수로 넘겨질때, 비효율적으로 사용되지 않도록 주의해서 설계해야 할 것이다.








C/C++ , 좋은 프로그래밍 - 템플릿 - 2




Effective C++. 이 책은 C++을 공부하던 사람이라면 한번 쯤은 읽었을 고전 명작이다. STL의 원리와 철학에 대해서 자세히 설명하고 독자로 하여금 새로운 개발 철학을 익히게 해준다.
그러나 아래 이책은 아는 사람이 많지 않다.



Effective C++이 효율성에 대해서 눈 뜨게 해 주었다면, Modern C++ design은 몰라서 전전긍긍 했을 때의 내 답답함을 해소해 주었다. 그러나 이 책에서 설명하는 핵심적인 기술 몇 가지는 C++11에서 지원해주거나 더 확장해서 지원한다. 이제는 직접 구현할 필요는 없을 지라도 공부에는 큰 도움을 줄 것이다.

템플릿의 기본적인 문법은 단순하다. 아래 처럼 템플릿을 선언한 후 형이 지정되면, 그에 맞춰 템플릿이 자동으로 클래스로 변환된다.

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); }
};
위의 예는 문자열 src에서 알 수 없는 형 TYPE의 값을 추출하기 위한 템플릿 클래스다. 개념적으로 TYPE은 어떠한 변수형 이라도 상관 없다. 그러나 문자열에서 특정한 형으로의 변환이 모두 같을 수는 없다. 정수형과 부동 소수점 형만 보더라도 atoi, atof 처럼 호출해야 하는 API가 달라진다. 우선 기본 변수형만 고려하여 보자.

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는 잘 모른다.... )






C/C++ , 좋은 프로그래밍 - 템플릿 - 1

클래스나 함수의 재사용률을 올리는 좋은 방법이 있을까? 뭐, 개발자마다 수 많은 의견이 나올 것이다. 그리고 그 수 많은 의견에서 템플릿 반드시 공통으로 언급될 것이다.

템플릿은 그 목적 자체가 재사용을 위해서 태어난 문법이나 다름 없다. 더군다나 템플릿 자체의 문법은 꽤 간단하다. 하지만 꽤 간단한 몇 가지 기능의 조합으로 아주 큰 효과를 발휘한다. 더군다나 C++11에 이르러 Variadic template 이라는 문법이 추가 되었다.

이제 기본적인 템플릿의 사용법부터 시작하여 본격적인 활용까지 이야기 해보고자 한다.

우선 템플릿 이야기를 하려면 먼저 알아야할 몇가지 문법을 언급하겠다.
가장 기본적으로 펑크터를 알아야 한다. 많은 사람이 알고 있겠지만 간단하게 이야기 해보고 시작하자.

class CFunctor
{
void operator()(int param){}
};

CFunctor functor;
functor(3);

기본적인 문법 구조는 위와 같다. 멤버 함수로 operator()() 함수를 정의하면 객체의 인스턴스를 함수처럼 호출 할 수 있다.

두번째로 함수 오버 로딩에 의한 자동 호출이다.

void func(int v){}
void func(double d){}

func(2)
func(2.0)

위의 예시처럼 func(2)와 func(2.0)은 각기 다른 함수가 호출된다. 이런 구조는 일반 함수 뿐만 아니라, 멤버 함수에도 적용된다.

이상으로 몇가지 기본적인 이야기는 마무리하고 본격적인 템플릿 이야기를 하자.


C/C++ , 좋은 프로그래밍 - 원칙

먼저 "어떤 프로그램이 나쁠까?"를 이야기 하자.

여러 문제를 댈 수 있겠지만, 나는 가장 중요한 것으로 에러 처리를 하지 않은 프로그램을 꼽는다.
프로그램이 개발되는 환경은 최적의 환경이다.
최신 라이브러리, 최신 PC, 검증된 네트워크 환경, 깔끔한 관리 등 최고의 환경에서 프로그램을 개발한다. 그러나 그 프로그램이 실행되는 곳은 최악인 경우가 종종 있다.
개발자의 개발 PC에서 나오지 않는 현상이 실제 유저의 손에서 동작하면 많은 버그를 뱉어낸다. QA를 통해서 일정 부분 걸러지기도 하지만 QA를 통과하는 버그는 반드시 존재한다.

에러 처리가 없다면 이런 버그는 심각한 문제를 유발할 때가 종종 있다. 에러 처리가 없는 그 부분에서 버그가 발생하는 것은 행복한 경우다. 그 버그가 돌고 돌아 엉뚱한 곳에서 문제를 일으킨다면 개발자의 PC에서 버그를 찾을 방법은 사실상 없다고 봐도 된다. 그 사용자의 PC와 똑같은 환경을 세팅하던지, 덤프 파일을 받아서 추적하지 않으면 버그를 찾는 것은 거의 불가능하다.


아무리 친숙한 API라도 API에 리턴 값이 존재하고 잘못 동작하는 경우가 명시되어 있다면 반드시 그 값을 검사해야 한다. 팀으로 작업을 하고 있다면 옆의 작업자를 믿으면 안된다. 그 작업자가 넘기는 파라미터와 결과 값은 반드시 검증하는 부분이 있어야 한다.  심지어 이런 원칙은 자기자신에게도 적용해야 한다. 스스로를 믿지 말고 값을 검증해야 한다.
추가로 반론의 여지가 있겠지만, assert의 사용은 자제하라고 하고 싶다. 대부분의 assert는 디버그 모드에서 동작한다. 디버그로 프로그램을 배포할 것이 아닌 이상, assert는 정말 확실한 부분에 최소로 사용하고, assert를 넣을 부분에 에러 처리 루틴을 스스로 만들어 사용하라.

에러 확인이라는 원칙과 더불어 중요한 것은 전체 프로그램의 에러 처리의 과정이다. 개인이면 개인적인 에러처리 과정이 있어야 하고 팀이면 팀 전체에서 유일하게 사용되는 에러 처리 과정이 있어야 한다. 팀 작업에서 에러 처리 과정에 개인 취향을 두어서는 안된다.
하나의 에러 처리 과정은 동료와 과거의 자신을 신뢰하게 하는 수단이다.

개인적으로, 과거에는 모든 클래스에 get_last_error 라는 함수를 두고, 모든 함수는 결과의 성공/실패를 리턴하게 만들었었다. 그러나 몇년간, 프로그램을 만들때마다 느낀 것은 '귀찮다'였다.  그래서 요즘은 try-catch를 사용한다. 단, 모든 예외는 STL의 std::exception만 사용하고 예외를 발생시킬 때는 반드시 이미 만들어 둔 매크로만 사용한다.
귀찮음 때문에 바꾸었지만 상당히 만족스럽다. 만들어 둔 매크로에는 에러 내용, 에러 번호, 발생 함수, 발생 줄 등의 모든 정보를 기록하기 때문에 한눈에 버그가 발생한 부분을 알 수 있다.
개인이든, 팀이든 에러 처리 과정을 반드시 정해 두길 바란다.

이 원칙을 지키고 있다면 어느 순간 자신이 만든 프로그램에 자신이 붙을 것이다. 에러가 발생해서 경고창이 떴다면 그에 맞는 대처를 설명해주면 된다. 또는 친절하게 에러를 설명했다면 사용자는 버그 내용은 레포트 하지도 않고 스스로 해결해서 사용할 것이다.






C/C++ , 좋은 프로그래밍 - 들어가며

좋은 프로그래밍에 대한 기준은 너무나 많다. 그러나 주관적인 관점에서 몇 가지 핵심적인 것만 축약한다면 몇가지로 나타낼 수 있다.

1. 협업을 쉽게 할 수 있는 구조이어야 한다.
2. 버그 추적을 쉽게할 수 있고 구조적으로 버그의 가능성을 줄여야 한다.
3. 유지 보수가 쉬워야 한다.
4. 재사용 가능한 코드를 모아서 관리해야 한다.

4가지 정도가 적당해 보인다.
사실, 기초적인 프로그래밍 지식과 API에 대한 숙지가 되어 있다면 결과물은 어떻게든 나오게 되어 있다.  C++의 개체지향 구조는 C로 바꾸어 만들 수 있고, C의 결과는 어셈블러로 바꾸어 표현할 수 있다. 왜 고급 언어를 쓰고 더 복잡한 프로그래밍 문법을 쓸까? 답은 단순하다 더 좋은 프로그램 결과를 더 짧은 시간에 더 효율적으로 만들기 위해서다.

지금부터는 지금까지 경험했던 여러가지 코드들 중 몇개를 골라서 소개할 것이다. 물론 주관적이기 때문에 다 동의하지 못할 수도 있다. 그러나 분명히 좋은 참고는 되리라 본다.

가장 먼저 템플릿을 설명할 것이다. 템플릿은 개발자라면 누구나 알고 있는 문법이지만 실제로 잘 활용하지 못하는 것 같다. 더구나 이미 개발된 좋은 템플릿 코드들도 쓰지 않는 개발자가 많다. 몇가지 예를 들며서 그 활용방안을 이야기 하자.
그리고 모듈화에 대하여 설명할 것이다. 모듈화라면 모두들 이미 알고 있겠지만 실제로 실천하는 개발자 역시 드물다. 여기서는 모듈화를 실제로 활용하면 왜 좋은지 설명하면서 몇년간 개발 하면서 만든 모듈화 관련 코드를 소개하겠다.
다음으로 Managed C++에 대해 소개하겠다. MS가 .NET을 소개하면서 만든, .NET용 C++이다. 최근의 Windows phone 및 windows 8이후의 APP 프로그램은 .NET C# 혹은 Visual basic, Managed C++을 이용해야 한다.

마지막으로 위의 것들을 종합해서 만든 개인 라이브러리 중 몇가지를 소개하겠다.

C++ 기준 설명이다.