본문 바로가기

C/C++/C/C++일반

함수 템플릿, 클래스 템플릿 을 사용하는 상황과 이유

한참 C++을 공부하던 학생 때 거즘 C++ 문법책을 다볼 쯤에 나오는 챕터가 있었으니 문법도 생소한 템플릿 이었다.

그땐 뭐 이런게 있나 싶어 한번 보고 신기하다 해서 쫌 보았는데 실제 코드로 밥을 먹고 살다보니 그리고 주된 업무가 개발이다 보니 템플릿을 쓸 기회가 별로 없었다 물론 STL을 사용해서 코드를 읽을 땐 쫌 보았지만 템플릿이란 걸 일부로 프로젝트에 적용해서 쓴적은 별로 없었던 것 같다. 알고는 있지만 활용방안을 잘 몰랐던 것이다. 그렇게 잘 안쓰게 되다보니 점점 템플릿이란 것과 멀어질 쯤 아! 이런 때 템플릿을 적용하면 되겠구나 싶은 기회가 생겨 이렇게 정리를 좀 하고자 한다.

 

템플릿이란 쉽게 말하자면 클래스나 함수에 사용되는 변수를 특정 형으로 정하지 않고 컴파일 할 때 결정되도록 기능을 지원하는 문법이라 할 수 있다. 쉽게 말한다 했는데 아무리 생각해도 쉽게 읽히진 않을 것 같다. 그냥 하나의 클래스 함수 가지고 여러가지 형의 데이터를 적용하기 위해 사용한다고 생각하자. 암튼~ 문법의 형태의 예를 보자

// 클래스 템플릿의 형태의 예
template <typename T>
class CTest
{
public:
     T ta;
     T tb;
};
// 함수 템플릿의 현태의 예
// typename, class 둘다 사용가능
template<class T>
T func1(T t1, T t2)
{
     return ( t1 + t2 );
}

기본적으로 템플릿을 사용하려면 위 클래스와 함수에 적용된 형태로 선언을 해야한다. 다음은 기본적인 사용예시이다.

// 템플릿 사용의 예
int main(int argc, char* argv)
{
    CTest<int> test1;
    test1.ta = 10;
    test1.tb = 20;
    int a = func1<int>(test1.ta, test1.tb);
    CTest<float> test2;
    test2.ta = 2.0f;
    test2.tb = 4.0f;
    float b = func1<float>(test2.ta, test2.tb);
    printf("Template a : %d, Template b : %f\n", a, b);
    return 0;
}

위 예를 보면 int 형과 float 형 데이터 타입을 동일한 클래스와 함수에 적용하여 사용할 수 있음을 알 수 있다. 그러면 이젠 위같은 성의없는 테스트 코드가 아닌 실제 업무에서 템플릿이 적용될 상황을 생각해보고 실제 템플릿을 코드에 적용해 보자. 상황은 tcp/ip 통신 프로그램에서 test_A 구조체와 test_B 구조체의 데이터를 전송해야하는 경우라 생각하자 우선 템플릿 생각하지 않고 코딩한 코드를 보자. 일단 이글은 템플릿에 관한 포스트이기 때문에 템플릿 설명에 불필요한 tcp/ip 관련 코드는 생략하겠다.

// 예제 1 템플릿 미적용
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")

struct test_A
{
    int nA;
    float fB;
};
struct test_B
{
    short nC;
    char szText[16];
};
struct testSend_A // test_A 데이터 전송용 구조체
{
    int header;
    int nSize;
    test_A A;
};
struct testSend_B  // test_B 데이터 전송용 구조체
{
    int header;
    int nSize;
    test_B B;
};
void send_A(SOCKET sock, const test_A &A) // test_A 데이터 전송 함수
{
    testSend_A sa;
    sa.header = 1;
    sa.nSize = sizeof(int)*2 + sizeof(test_A);
    send(sock, (char*)&A, sa.nSize, 0);
}
void send_B(SOCKET sock, const test_B &B) // test_B 데이터 정송 함수
{
    testSend_B sb;
    sb.header = 1;
    sb.nSize = sizeof(int)*2 + sizeof(test_A);
    send(sock, (char*)&B, sb.nSize, 0);
}
int main(int argc, char* argv)
{
    // tcp/ip 초기화 관련 코드 생략
    // .......
    // .........
    //
    
    SOCKET sock = socket(PF_INET, SOCK_STREAM, 0);
    // connect 관련 코드 생략
    // .......
    // .........
    //
    test_A a;
    a.fB = 1.0f;
    a.nA = 2;
    test_B b;
    b.nC = 10;
    sprintf(b.szText, "Using template!");
    send_A(sock, a);
    send_B(sock, b);
    return 0;
}

간단하게 샘플 코드 만들려 했는데 하다 보니 좀 길어 졌다. 그래도 코드를 보기에는 별 어려움이 없을 것이다. 쉽다. test_A 와 test_B 구조체에 값을 넣어 전송시키는 코드이다. 그런데 두 구조체를 전송하는데 두개의 전송용 구조체와 두개의 함수가 필요했다. 전송해야하는 구조체 종류가 100개라면 어덯게 할 것인가 다 일일이 전송 함수를 만들어 줄 것인가??? 필자의 경우 예전에 다 만들어 주진 않았지만 그래도 엄청난 함수를 코딩해야만 했다. 전송 프로토콜의 종류가 500가지나 되었다. ㅠㅠ 물론 위의 코드의 경우 함수를 구조체의 종류별로 다 만들어줄 필요는 없다. 함수의 구조체 파라미터를 포인터로 변경하고 구조체의 크기 파라미터를 추가해주면 해결된다.

// 개선된 전송함수 (템플릿 미적용)
void send_data(SOCKET sock, int header, void* pData, int nDataSize)
{
    // nDataSize : 데이터의 크기
    int nMsgSize = sizeof(int)+sizeof(int)+nDataSize;    // 전송 메시지 사이즈
    char *pSendData = new char[];
     
    int idx = 0;
     
    memcpy( &pSendData[idx], &header, sizeof(int));    // 메시지 헤더 설정
    idx += sizeof(int);  // 전송 데이터 버퍼의 인덱스를 증가시킨다.
     
    memcpy(&pSendData[idx], &nMsgSize, sizeof(int));   // 메시지 사이즈
    idx += sizeof(int);  // 전송 데이터 버퍼의 인덱스를 증가시킨다.
     
    memcpy(&pSendData[idx], pData, nDataSize);   // 전송 데이터
     
    send(sock, pSendData, nMsgSize, 0);  // 데이터 전송
     
    delete []pSendData;
}

데이터 전송 함수를 수정해서 하나의 함수를 가지고 여러 타입의 데이터를 전송할 수 있도록 했다. 뭐 이정도면 나쁘지 않다. 그러나 좋지도 않다. 부담스러운 동적할 당에 코드가 읽기도 불편하고 관리도 불편하다 그냥 탁 받아서 탁 보네는 간단한 코드를 사용하고 싶다. 그럼 템플릿을 적용한 아래의 코드를 살펴보자.

// 템플릿을 적용한 구조체와 함수
template<typename T>
struct testSend // 데이터 전송용 구조체
{
    int header;
    int nSize;
    T t;
    };
     
    template<typename T>
    void send_data(SOCKET sock, int header, T t)
    {
    testSend<T> sData; // 데이터 전송용 구조체 선언
     
    sData.header = header;
    sData.nSize = sizeof(int)+sizeof(int)+sizeof(T);  // 전송 메시지 사이즈
    sData.t = t;
     
    send(sock, (char*)&sData, sData.nSize, 0);
}

구조체도 템플릿 적용이 가능하다. 템플릿을 적용한 코드와 적용하지 않은 코드를 비교하기 바란다. 코드량도 코드량이지만 코드를 읽기가 정말 편하고 쉽다. 물론 템플릿 문법을 알고 있는 경우를 말하는 것이다. 어떠한 문법이나 규칙을 공부할 때 그 공부의 결과로 기존의 상황을 개선할 수 있다면 그 공부는 피가되고 살이되는 공부인 것이다. 위의 함수에 STL을 적용하면 훨씬더 유연한 코드가 될 수 있다.

// STL vector를 적용한 전송함수 선언
void send_data(SOCKET sock, int header, const vector<T> &vecData)

마지막으로 함수 템플릿 호출하는 코드이다.

// 함수 템플릿 호출 코드예시
test_A a;
send_data<test_A>(sock, 1, a);

'C/C++ > C/C++일반' 카테고리의 다른 글

2차원 배열 매개변수 전달 방법  (0) 2015.10.30
파일 포인터 / feof() 함수 사용 tip  (0) 2015.10.30
함수 포인터  (0) 2015.10.30
가상소멸자 사용  (0) 2015.10.30
재귀함수와 비재귀 함수 성능비교 코드  (0) 2014.10.09