| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 |
- c# winform
- 다트 언어
- git
- c# 윈폼
- docker
- c# 추상 클래스
- HTML
- gitlab
- dart 언어
- 구조체
- Flutter
- Unity
- Data Structure
- C++
- jupyter
- 깃
- vim
- Python
- Algorithm
- jupyter lab
- Houdini
- c언어
- C# delegate
- 유니티
- c#
- 포인터
- C언어 포인터
- 도커
- github
- 플러터
- Today
- Total
nomad-programmer
[Programming/C++] STL을 위한 템플릿 예제 본문
For_each()는 배열의 원소를 출력하는 함수이다. 이 함수는 배열의 시작 주소와 끝 주소를 인자로 받아 모든 원소에 대해 클라이언트 함수를 호출한다.
#include <iostream>
#include <string>
using namespace std;
// begin은 배열의 시작 주소, end는 배열의 끝 주소,
// fp는 클라이언트 함수 포인터이다.
void For_each(int* begin, int* end, void (*fp)(int))
{
while (begin != end)
{
fp(*begin++);
}
}
void PrintInt(int n)
{
cout << n << " ";
}
int main()
{
int arr[5] = { 10, 20, 30, 40, 50 };
For_each(arr, arr + 5, PrintInt);
cout << endl;
return 0;
}
이 예제의 For_each() 함수는 배열의 원소가 정수일 때만 사용 가능하다. For_each() 함수가 원소의 타입에 상관없이 사용할 수 있는 일반적인 함수라면 클라이언트의 활용도를 높이고 유지 보수를 좋게 할 수 있다.
다음은 배열의 원소 타입을 클라이너트가 결정하게 타입을 일반화한 For_each() 함수 템플릿 예제이다.
#include <iostream>
#include <string>
using namespace std;
template<typename IterT, typename FuncT>
void For_each(IterT begin, IterT end, FuncT func)
{
while (begin != end)
{
func(*begin++);
}
}
void PrintInt(int n)
{
cout << n << " ";
}
void PrintString(const string& str)
{
cout << str << " ";
}
int main()
{
int arr[5] = { 10, 20, 30, 40, 50 };
For_each(arr, arr + 5, PrintInt);
cout << endl;
string strArr[4] = { "Hello", "World", "C++", "20" };
For_each(strArr, strArr + 4, PrintString);
cout << endl;
return 0;
}
// 결과
10 20 30 40 50
Hello World C++ 20
여기서 컴파일러는 클라이언트 코드를 보고 For_each() 함수 템플릿의 두 인스턴스 For_each<int*, void(*)(int)>()와 For_each<string*, void(*)(const string)>()를 만들어 낸다.
다음은 명시적으로 함수 템플릿을 호출한 예이다.
int main()
{
int arr[5] = { 10, 20, 30, 40, 50 };
For_each<int*, void(*)(int)>(arr, arr + 5, PrintInt);
cout << endl;
string strArr[4] = { "Hello", "World", "C++", "20" };
For_each<string*, void(*)(const string&)>(strArr, strArr + 4, PrintString);
cout << endl;
return 0;
}
다음으로 PrintInt()와 PrintString()이 다른 것은 타입뿐이므로 이 출력 함수도 템플릿 함수로 작성할 수 있다.
#include <iostream>
#include <string>
using namespace std;
template<typename IterT, typename FuncT>
void For_each(IterT begin, IterT end, FuncT func)
{
while (begin != end)
{
func(*begin++);
}
}
template<typename T>
void Print(T data)
{
cout << data << " ";
}
int main()
{
int arr[5] = { 10, 20, 30, 40, 50 };
//For_each<int*, void(*)(int)>(arr, arr + 5, Print<int>);
For_each(arr, arr + 5, Print<int>);
cout << endl;
string strArr[4] = { "Hello", "World", "C++", "20" };
//For_each<string*, void(*)(const string&)>(strArr, strArr + 4, Print<const string&>);
For_each(strArr, strArr + 4, Print<const string&>);
cout << endl;
return 0;
}
여기서 주의할 점은 출력 함수의 템플릿 매개변수를 컴파일러가 유추할 수 없으므로 명시적으로 매개변수 인자(Print<int>, Print<const string&>)를 지정해야 한다.
또는 아래와 같이 명시적으로!
For_each<int*, void(*)(int)>(arr, arr + 5, Print);
For_each<string*, void(*)(const string&)>(strArr, strArr + 4, Print);
마지막으로 출력 함수 Print()를 함수 객체로 바꾼다. 함수 객체를 사용하면 부가적인 서비스를 함수 객체가 제공하게 할 수 있다.
#include <iostream>
#include <string>
using namespace std;
template<typename IterT, typename FuncT>
void For_each(IterT begin, IterT end, FuncT func)
{
while (begin != end)
{
func(*begin++);
}
}
template<typename T>
struct PrintFunctor
{
string sep; // 출력 구분자 정보
explicit PrintFunctor(const string& s = " ")
: sep(s) { }
void operator()(T data) const
{
cout << data << sep;
}
};
int main()
{
int arr[5] = { 10, 20, 30, 40, 50 };
For_each(arr, arr + 5, PrintFunctor<int>());
cout << endl;
string strArr[4] = { "Hello", "World", "C++", "20" };
For_each(strArr, strArr + 4, PrintFunctor<const string&>("*\n"));
cout << endl;
return 0;
}
// 결과
10 20 30 40 50
Hello*
World*
C++*
20*
함수 객체는 부가정보를 가질 수 있으므로 sep이라는 출력 패턴 구분자를 가진다. 정수는 디폴트 출력 구분자로 " "를 사용하며 문자열을 출력 구분자로 "*\n"을 사용한다.
템플릿의 매개변수와 함수 객체를 결합하면 반환 타입과 함수 매개변수 타입을 클라이언트가 결정하는 아주 유연한 함수 객체를 만들 수 있다.
다음은 함수 객체의 반환 타입과 매개변수 타입을 클라이언트가 결정하여 함수 객체를 만들 수 있는 간단한 예이다.
#include <iostream>
#include <string.h>
using namespace std;
template<typename RetType, typename ArgType>
class Functor
{
public:
RetType operator() (ArgType data)
{
cout << data << endl;
return RetType();
}
};
int main()
{
Functor<void, int> functor1;
functor1(10);
Functor<bool, string> functor2;
functor2("Hello!");
return 0;
}
여기서 "RetType()" 을 살펴보자. 왜 "( )"를 붙일까?
템플릿에서 RetType이 어떤 타입이 올지 모르기 때문에, 모든 타입에 대해 안전하게 동작하는 방식이 필요하다.
// 기본 타입
int() //0
bool() // false
float() // 0.0f
double() // 0.0
char() // '\0'
// 포인터 타입
int*() // nullptr
// 클래스 타입
std::string() // 기본 생성자 호출 -> 빈 문자열
MyClass() // 기본 생성자 호출
// void 타입(특수 케이스)
void() // 아무것도 반환하지 않음
만약 "( )" 없이 return RetType; 이라고 쓴다면 컴파일 에러가 난다. 그 이유는, RetType은 타입 이름이지 값이 아니다. 따라서 타입을 반환할 수는 없기 때문이다.
다음은 STL에서 데이터의 쌍을 표현할 때 항상 사용되는 pair 클래스를 템플릿으로 구현해 보자.
#include <iostream>
#include <string.h>
using namespace std;
template<typename T1, typename T2>
struct Pair
{
T1 first;
T2 second;
Pair(const T1& ft, const T2& sd)
: first(ft), second(sd) { }
};
int main()
{
Pair<int, int> p1(10, 20);
cout << p1.first << ',' << p1.second << endl;
Pair<int, string> p2(1, "one");
cout << p2.first << ',' << p2.second << endl;
cout << endl;
/// 여기서부터 STL의 pair 사용
pair<int, int> p3(10, 20);
cout << p3.first << ',' << p3.second << endl;
pair<int, string> p4(1, "one");
cout << p4.first << ',' << p4.second << endl;
return 0;
}
// 결과
10,20
1,one
10,20
1,one
'Programming > C++' 카테고리의 다른 글
| [Programming/C++] 예외 처리 생략과 실패 대응 (0) | 2024.09.15 |
|---|---|
| [Programming/C++] 사용자 정의 리터럴 (0) | 2024.09.09 |
| [Programming/C++] vcpkg (0) | 2024.08.05 |
| [Programming/C++] 연산자 오버로딩의 대한 예제 (0) | 2023.06.14 |
| [Programming/C++] 함수 포인터의 변형 (함수 포인터 배열 관련) (0) | 2023.03.02 |
