Notice
Recent Posts
Recent Comments
Link
«   2025/11   »
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
Archives
Today
Total
관리 메뉴

nomad-programmer

[Programming/C++] STL을 위한 템플릿 예제 본문

Programming/C++

[Programming/C++] STL을 위한 템플릿 예제

scii 2025. 11. 19. 01:44

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

 

Comments