C에서는 va_list를 사용하여 가변인자를 처리해야 했다.
va_start로 시작해서 va_end로 끝내야 했으며, 어떤 타입인지 미리 알려주어야 하거나 모든 같은 형으로 가변인자를 사용해야 했다. 불편했고, C 마지막에 printf와 scanf의 동작원리로 배운 뒤로는 써 본 적이 없던 개념이다.
C++로 와서 template 이라는 개념이 생겼다. 어떤 형태라도 template으로 뭉뚱그려 인자로 받을 수 있게 된 것이다. 그리고 가변인자 역시 template, 탬플릿으로 받을 수 있다.
가변인자 탬플릿은 다음과 같이 선언한다.
template<typename 첫번째_가변인자_인수>
반환형 함수명(첫번째_가변인자_인수... 가변인자_변수);
타입이름 앞에 ... 을 붙여 가변인자 탬플릿이라고 알려준다.
한글로 쓴 이름은 원하는 타입을 적으면 된다. 이렇게.
template<typename... Args>
void print(Args... args)
{
...
print(args...);
}
매개변수로 쓸 경우 변수명의 뒤에 ...을 붙여준다.
탬플릿 선언 : typename의 뒤에 : typename... Ty
선언 : 타입의 뒤에 ... : void print(Ty... ty);
사용 : 변수의 뒤에 ... : print(ty...);
그런데 이렇게 쓰면 가변 인자의 내용을 확인을 못 하기 때문에 다음과 같이 '첫 번째 가변 인자'를 명시한다.
template<typename 첫번째_가변인자_인수, typename... 가변인자_타입이름>
반환형 함수명( 첫번째_가변인자_인수 변수명, 가변인자_타입이름... 가변인자_변수);
예를 들면 이렇게 적으면 된다.
template<typename Ty, typename ... Args>
void print(Ty ty, Args...args)
'가변인자_변수'는 첫 번째 인자를 제외한 남은 가변인자를 가지고 '첫번째_가변인자_인수'만 첫 번째 인자로들어가게 된다. 이걸 재귀적으로 시행하는 것이 가변인자 함수의 핵심이다.
그런데 위 예제에서는 Ty라는 탬플릿으로 인자를 받았기 때문에 Ty가 어떤 타입인지 알 수 없다. C++을 공부한사람들이라면 이 때 사용할 만한 좋은 개념을 알 것이다.
함수 오버로딩(overloading : 매개인자에 따라 함수명이 동일한 함수들을 재정의하는 기능)을 사용하는 것이다.
그러면 사용 예는 다음과 같아진다.
void printType(int i);
void printType(string s);
...
template<typename Ty, typename ... Args>
void print(Ty ty, Args...args)
{
printType(ty);
print(args...);
}
이쯤에서 의문이 하나 생긴다. 가변인자가 하나씩 빠진 끝에 가변인자가 하나도 남지 않을 경우에는?
가변인자 탬플릿을 사용할 경우, 위와 같은 문제를 해결하기 위해 매개인자가 없거나, 마지막에 올 매개인자를가지는, "가변인자 탬플릿 함수와 동일한 함수 이름을 가지는" 함수를 오버로딩 해야 한다. 이 함수는 가변인자를 모두 처리한 뒤의 후처리를 위한 함수로, C에서의 va_end() 와 동일한 역할을 수행한다. 차이점은, va_end()는 없다고 컴파일 에러를 부르지 않지만 가변인자 탬플릿은 원하는 형식의 함수가 오버로딩되지 않으면 컴파일 오류를 뿜는다.
가변인자의 마지막을 위한 함수는 대체로 매개인자가 없는 함수가 되는 게 좋은데, 좀 더 일반적인 후처리가 가능하기 때문이다. 마지막에 올 매개인자를 강제하는 방법을 쓰면 그 이외의 인자가 가변인자의 마지막에 올 때컴파일 에러를 뿜는다. 유지보수에 문제가 생길 여지가 있기 때문에 매개인자가 없는 함수를 사용하는 것이 좋다.
예시는 다음과 같다.
// 1. 매개인자가 없는 오버로딩 함수
// 안정적인 후처리가 가능
void print();
void printType(int i);
void printType(string s);
...
template<typename Ty, typename ... Args>
void print(Ty ty, Args...args)
{
printType(ty);
print(args...);
}
// 2. 마지막 인자를 강제할 경우
// 마지막은 항상 int형으로만 받으려고 한다.
void print(int last_arg);
void printType(int i);
void printType(string s);
...
template<typename Ty, typename ... Args>
void print(Ty ty, Args...args)
{
printType(ty);
print(args...);
}
이 때, 마지막 매개인자를 강제하는 오버로딩의 인자는 여러 개가 될 수도 있다. 그러니까 마지막 매개인자 5개를 강제하는 함수를 작성할 수도 있다는 사실.
void print(int last_arg_01, int last_arg_02, constchar* last_arg_03, int last_arg_04);
다른 말로는 마지막 매개인자들의 순서에 따라 후처리를 다르게 해 줄 수도 있다는 뜻인데, 그런 걸 하시는 분에게는 이 책을 추천한다.
자세한 내용을 원한다면 MSDN을 참고하는 게 좋다. 한글 번역이 잘 돼 있다.
MSDN : https://msdn.microsoft.com/ko-kr/library/dn439779(v=vs.140).aspx
+) 매개인자는 오직 뒤에만 사용할 수 있다.
+) 매개인자의 사이즈는 sizeof...() 키워드로 알 수 있다.
+) 반환형이 const size_t 이기 때문에 배열을 선언할 수 있다.
질문은 댓글로 남겨주세요.
'프로그래밍 > C++' 카테고리의 다른 글
[C++17] filesystem (0) | 2017.05.22 |
---|---|
[C++] invoke 함수의 동작원리를 통해 설명하는 템플릿 부분 특수화와 SFINAE (0) | 2016.08.25 |
[C++11] 균일 초기화(중괄호 초기화) (0) | 2016.07.13 |
[C++11] enum class (0) | 2016.07.13 |