'2014/01/10'에 해당되는 글 18건

  1. 2014.01.10 [펌] About STL : C++ STL 프로그래밍(2-2)
  2. 2014.01.10 [펌] About STL : C++ STL 프로그래밍(2-1)
  3. 2014.01.10 [펌] About STL : C++ STL 프로그래밍(1)
  4. 2014.01.10 숫자에 세자리마다 콤마를 찍는 알고리즘
  5. 2014.01.10 Duff's Device
  6. 2014.01.10 [iTween] 유도 미사일 구현 관련 내용
  7. 2014.01.10 [좌표] 좌표에 대해서 알아봅시다(convertToWorldSpace) 절대좌표
  8. 2014.01.10 [블로그] 블로그에 코딩 색 입히는 방법.

[펌] About STL : C++ STL 프로그래밍(2-2)

|

제공: 한빛 네트워크
저자: 최흥배
이전기사:

이전 기사에서는 함수 템플릿에 대해 설명을 했으니 이번에는 클래스 템플릿에 대해서 설명하려고 합니다. 클래스 템플릿을 아주 간단하게 말하면 함수 템플릿이 함수에 템플릿을 사용한 것처럼 클래스 템플릿은 클래스에 템플릿을 사용한 것입니다. 

그러니 함수 템플릿에 대해서 잘 모르시는 분은 꼭 함수 템플릿에 대한 글을 먼저 보고 이 글을 보는 것이 이해하기에 좋습니다.

경험치 변경 이력 저장

기획팀에서 유저들이 게임에 접속하여 다른 유저들과 100번의 게임을 했을 때 유저들의 경험치가 변경 되는 이력을 볼 수 있기를 요청 하였습니다. 

기획팀의 요구를 들어주기 위해서 저는 게임이 끝날 때마다 경험치를 저장합니다. 또 경험치 이력 내역을 출력할 때 가장 최신에서 가장 오랜 된 것을 보여줘야 되기 때문에 스택(stack)이라는 자료 구조를 사용합니다.

스택은 자료 구조 중의 하나로 가장 마지막에 들어 온 것을 가장 먼저 꺼내는 LIFO(Last In First Out) 형식으로 되어 있습니다. 데이터를 넣을 때를 push, 빼낼 때는 pop이라는 이름을 일반적으로 사용한다.

경험치 이력을 저장하는 클래스의 구현과 이것을 사용하는 것은 아래와 같습니다. 

// 경험치를 저장할 수 있는 최대 개수
const int MAX_EXP_COUNT = 100;

// 경험치 저장 스택 클래스
class ExpStack
{
public:
  ExpStack()
  {
    Clear();
  }

  // 초기화 한다.
  void Clear()
  {
    m_Count = 0;
  }

  // 스택에 저장된 개수
  int Count()
  {
    return m_Count;
  }

        // 저장된 데이터가 없는가?
  bool IsEmpty()
  {
    return 0 == m_Count ? true : false;
  }

  // 경험치를 저장한다.
  bool push( float Exp )
  {
    // 저장할 수 있는 개수를 넘는지 조사한다.
    if( m_Count >= MAX_EXP_COUNT )
    {
      return false;
    }

    // 경험치를 저장 후 개수를 하나 늘린다.
    m_aData[ m_Count ] = Exp;
    ++m_Count;

    return true; 
  }

  // 스택에서 경험치를 빼낸다.
  float pop()
  {
    // 저장된 것이 없다면 0.0f를 반환한다.
    if( m_Count  < 1 )
    {
      return 0.0f;
    }

    // 개수를 하나 감소 후 반환한다.
    --m_Count;
    return m_aData[ m_Count ];
  }

private:
  float  m_aData[MAX_EXP_COUNT];
  int    m_Count;
};

#include 

using namespace std;

void main()
{
  ExpStack kExpStack;
  
  cout << "첫번째 게임 종료- 현재 경험치 145.5f" << endl;
  kExpStack.push( 145.5f );

  cout << "두번째 게임 종료- 현재 경험치 183.25f" << endl;
  kExpStack.push( 183.25f );

  cout << "세번째 게임 종료- 현재 경험치162.3f" << endl;
  kExpStack.push( 162.3f );


  int Count = kExpStack.Count();
  for( int i = 0; i < Count; ++i )
  {
    cout << "현재 경험치->" << kExpStack.pop() << endl;
  }
}

실행 결과 

그림1 

실행 결과를 보면 알 수 있듯이 스택 자료구조를 사용하였기 때문에 제일 뒤에 넣은 데이터가 가장 제일 먼저 출력 되었습니다.

게임 돈 변경 이력도 저장해 주세요

경험치 변경 이력을 저장하고 출력하는 기능을 만들어서 기획팀에 보여주니 이번에는 게임 돈의변경 이력도 보고 싶다고 말합니다. 

위에서 경험치 변경 이력 저장 기능을 만들어 보았으니 금방 할 수 있는 것이죠. 그래서 이번에는 이전 보다 훨씬 더 빨리 만들었습니다. 

// 돈을 저장할 수 있는 최대 개수
const int MAX_MONEY_COUNT = 100;

// 돈 저장 스택 클래스
class MoneyStack
{
public:
  MoneyStack()
  {
    Clear();
  }

  // 초기화 한다.
  void Clear()
  {
    m_Count = 0;
  }

  // 스택에 저장된 개수
  int Count()
  {
    return m_Count;
  }

  // 저장된 데이터가없는가?
  bool IsEmpty()
  {
    return 0 == m_Count ? true : false;
  }

  // 돈을 저장한다.
  bool push( __int64 Money )
  {
    // 저장 할 수 있는 개수를 넘는지 조사한다.
    if( m_Count >= MAX_MONEY_COUNT )
    {
      return false;
    }

    // 저장후 개수를 하나 늘린다.
    m_aData[ m_Count ] = Money;
    ++m_Count;

    return true; 
  }

  // 스택에서 돈을 빼낸다.
  __int64 pop()
  {
    // 저장된 것이 없다면 0을 반환한다.
    if( m_Count  < 1 )
    {
      return 0;
    }

    // 개수를 하나 감소 후 반환한다.
    --m_Count;
    return m_aData[ m_Count ];
  }

private:
  __int64  m_aData[MAX_MONEY_COUNT];
  int  m_Count;
};

ExpStack 클래스와 MoneyStack 클래스가 비슷합니다 

게임 돈 변경 이력 저장 기능을 가지고 있는 MoneyStack 클래스를 만들고 보니 앞에 만든 ExpStack와 거의 같습니다. 저장하는 데이터의 자료형만 다를뿐이지 모든 것이 같습니다. 그리고 기획팀에서는 게임 캐릭터의 Level 변경 이력도 저장하여 보여주기를 바라는 것 같습니다. 이미 거의 똑같은 클래스를 두개 만들었고 앞으로도 기획팀에서 요청이 있으면 더 만들 것 같습니다. 이렇게 자료형만 다른 클래스를 어떻게 하면 하나의 클래스로 정의 할수 있을까요? 이와 비슷한 문제를 이전의 "함수 템플릿"에서도 나타나지 않았나요? 그때 어떻게 해결했죠?(생각나지 않는 분들은 앞의 "함수 템플릿"을 다시 한번 봐 주세요 ^^) 

템플릿으로 하면됩니다. 

기능은 같지만 변수의 자료형만 다른 함수를 템플릿을 사용하여 하나의 함수로 정의했듯이 이번에는 템플릿을 사용하여 클래스를 정의합니다. 클래스에서 템플릿을 사용하면 이것을 클래스 템플릿이라고 합니다. 클래스 템플릿을 사용하면 위에서 중복된 클래스를 하나의 클래스로 만들 수 있습니다.

클래스 템플릿을 사용하는 방법

클래스 템플릿을 정의하는 문법은 아래와 같습니다. 

그림2 

정의한 클래스 템플릿을 사용하는 방법은 아래와 같습니다. 

그림3

Stack 템플릿 클래스

지금까지 만들었던 ExpStack 과 MoneyStack을 클래스 템플릿으로 만든 코드는 아래와 같습니다. 

const int MAX_COUNT = 100;

template<typename T> 
class Stack
{
public:
  Stack()
  {
    Clear();
  }

  // 초기화 한다.
  void Clear()
  {
    m_Count = 0;
  }

  // 스택에 저장된 개수
  int Count()
  {
    return m_Count;
  }

  // 저장된 데이터가 없는가?
  bool IsEmpty()
  {
    return 0 == m_Count ? true : false;
  }

  // 데이터를 저장한다.
  bool push( T data )
  {
    // 저장 할수 있는 개수를 넘는지 조사한다.
    if( m_Count >= MAX_COUNT )
    {
      return false;
    }

    // 저장후 개수를 하나 늘린다.
    m_aData[ m_Count ] = data;
    ++m_Count;

    return true; 
  }

  // 스택에서 빼낸다.
  T pop()
  {
    // 저장된 것이 없다면 0을 반환한다.
    if( m_Count  < 1 )
    {
      return 0;
    }

    // 개수를 하나 감소 후 반환한다.
    --m_Count;
    return m_aData[ m_Count ];
  }

private:
  T  m_aData[MAX_COUNT];
  int    m_Count;
};

#include 

using namespace std;

void main()
{
  Stack kStackExp;
  
  cout << "첫번째 게임 종료- 현재 경험치 145.5f" << endl;
  kStackExp.push( 145.5f );

  cout << "두번째 게임 종료- 현재 경험치 183.25f" << endl;
  kStackExp.push( 183.25f );

  cout << "세번째 게임 종료- 현재 경험치 162.3f" << endl;
  kStackExp.push( 162.3f );


  int Count = kStackExp.Count();
  for( int i = 0; i < Count; ++i )
  {
    cout << "현재 경험치->" << kStackExp.pop() << endl;
  }

  cout << endl << endl;

  Stack<__int64> kStackMoney;
  
  cout << "첫번째 게임 종료- 현재 돈 1000023" << endl;
  kStackMoney.push( 1000023 );

  cout << "두번째 게임 종료- 현재 돈 1000234" << endl;
  kStackMoney.push( 1000234 );

  cout << "세번째 게임 종료- 현재 돈 1000145" << endl;
  kStackMoney.push( 1000145 );


  Count = kStackMoney.Count();
  for( int i = 0; i < Count; ++i )
  {
    cout << "현재 돈->" << kStackMoney.pop() << endl;
  }
}

실행 결과 

그림4 

클래스 템플릿으로 Stack을 구현하여 앞으로 다양한 데이터를 사용할 수 있게 되었습니다. 

그런데 위의 Stack 클래스는 부족한 부분이 있습니다. 앞으로 이 부족한 부분을 채워 나가면서 클래스 템플릿에 대해서 좀 더 알아 보겠습니다.

클래스 템플릿에서 non-type 파라메터 사용

위에서 만든 Stack 클래스는 데이터를 저장할 수 있는 공간이 100개로 정해져 있습니다. Stack의 크기는 사용하는 곳에 따라서 변동될 수 있어야 사용하기에 적합합니다. 

함수 템플릿을 설명할 때도 non-type이 나왔는데 사용 방법이 거의 같습니다. 템플릿 파라메터를 기본 데이터 형으로 합니다. 아래의 사용 예를 보시면 금방 이해가 갈 것입니다. 

// 템플릿 파라메터중 int Size가 non-type  파라메터입니다.
template<typename T, int Size> 
class Stack
{
public:
  Stack()
  {
    Clear();
  }

  // 초기화 한다.
  void Clear()
  {
    m_Count = 0;
  }

  // 스택에 저장된 개수
  int Count()
  {
    return m_Count;
  }

  // 저장된 데이터가 없는가?
  bool IsEmpty()
  {
    return 0 == m_Count ? true : false;
  }

  // 데이터를 담을수 있는 최대 개수
  int GetStackSize()
  {
    return Size;
  }

  // 데이터를 저장한다.
  bool push( T data )
  {
    // 저장할 수 있는 개수를 넘는지 조사한다.
    if( m_Count >= Size )
    {
      return false;
    }

    // 저장 후 개수를 하나 늘린다.
    m_aData[ m_Count ] = data;
    ++m_Count;

    return true; 
  }

  // 스택에서 빼낸다.
  T pop()
  {
    // 저장된 것이 없다면 0을 반환한다.
    if( m_Count  < 1 )
    {
      return 0;
    }

    // 개수를 하나 감소 후 반환한다.
    --m_Count;
    return m_aData[ m_Count ];
  }

private:
  T  m_aData[Size];
  int  m_Count;
};

#include 

using namespace std;

void main()
{
  Stack kStack1;
  cout << "스택의 크기는?" << kStack1.GetStackSize() << endl;

  Stack kStack2;
  cout << "스택의 크기는?" << kStack2.GetStackSize() << endl;
}

실행 결과 

그림5

템플릿 파라메터 디폴트 값 사용

일반 함수에서 함수 인자의 디폴트 값을 지정하듯이 클래스 템플릿의 파라메터도 디폴트 값으로 할 수 있습니다. 

// 템플릿 파라메터중 int Size가 non-type 파라메터입니다. 
// Size의 디폴트 값을 100으로 합니다.
template<typename T, int Size=100> 
class Stack
{
   …..  생략
}

void main()
{
  Stack kStack1;
  cout << "스택의크기는?" << kStack1.GetStackSize() << endl;

  Stack kStack2;
  cout << "스택의크기는?" << kStack2.GetStackSize() << endl;
}

List5에서 템플릿 파라메터 중 Size의 값을 디폴트 100으로 했습니다. 클래스를 생성할 때 두 번째 파라메터 값을 지정하지 않으면 디폴트 값이 사용 됩니다. 

실행 결과 

그림6

스택 클래스의 크기를 클래스 생성자에서 지정

클래스 템플릿에 대한 설명을 계속 하기 위해 현재까지 만든 스택 클래스를 변경합니다. 스택의 크기를 클래스 템플릿 파라메터가 아닌 생성자에서 지정하도록 변경하겠습니다. 

template<typename T, int Size=100> 
class Stack
{
public:
  explicit Stack( int size )
  {
    m_Size = size;
    m_aData = new T[m_Size];

    Clear();
  }

  ~Stack()
  {
    delete[] m_aData;
  }

  // 초기화 한다.
  void Clear()
  {
    m_Count = 0;
  }

  // 스택에 저장된 개수
  int Count()
  {
    return m_Count;
  }

  // 저장된 데이터가 없는가?
  bool IsEmpty()
  {
    return 0 == m_Count ? true : false;
  }

  // 데이터를 담을 수 있는 최대 개수
  int GetStackSize()
  {
    return m_Size;
  }

  // 데이터를 저장한다.
  bool push( T data )
  {
    // 저장할 수 있는 개수를 넘는지 조사한다.
    if( m_Count >= m_Size )
    {
      return false;
    }

    // 저장 후 개수를 하나 늘린다.
    m_aData[ m_Count ] = data;
    ++m_Count;

    return true; 
  }

  // 스택에서 빼낸다.
  T pop()
  {
    // 저장된 것이 없다면 0을 반환한다.
    if( m_Count  < 1 )
    {
      return 0;
    }

    // 개수를 하나 감소 후 반환한다.
    --m_Count;
    return m_aData[ m_Count ];
  }

private:
  T*  m_aData;
  int  m_Count;

  int m_Size;
};

#include 

using namespace std;


void main()
{
  Stack kStack1(64);
  cout << "스택의 크기는? " << kStack1.GetStackSize() << endl;
}

실행결과 

그림7 

List 6의 코드에서 잘 보지 못한 키워드가 있을 것입니다. 바로 explicit 입니다. explicit 키워드로 규정된 생성자는 암시적인 형 변환을 할 수 없습니다. 그래서 List6의 void main()에서

  Stack kStack1 = 64; 

로 클래스를 생성하면 컴파일 에러가 발생합니다.

클래스 템플릿 전문화

기획팀에서 새로운 요구가 들어왔습니다. 이번에는 게임을 할 때 같이 게임을 했던 유저의 아이디를 저장하여 보여주기를 원합니다. 지금까지 만든 Stack 클래스는 기본 자료형을 사용하는 것을 전제로 했는데 유저의 아이디를 저장하려면 문자열이 저장되어야 하므로 사용할 수가 없습니다. 

기본 자료형으로 하지 않고 문자열을 사용한다는 것만 다르지 작동은 비슷하므로 Stack이라는 이름의 클래스를 사용하고 싶습니다. 기존의 Stack 클래스 템플릿과 클래스의 이름만 같지 행동은 다른 Stack 클래스를 구현 하려고 합니다. 이때 필요한 것인 클래스 템플릿의 전문화라는 것입니다. 클래스 템플릿 전문화는 기존에 구현한 클래스 템플릿과 이름과 파라메터 개수는 같지만 파라메터를 특정한 것으로 지정합니다. 

전문화된 클래스 템플릿 정의는 다음과 같은 형태를 가진다.

template <> 
class 클래스 이름<지정된 타입>
{
   ……………….
};

아래의 코드는 문자열을 저장하기 위해 char* 으로 전문화한 Stack 클래스입니다. 

// ID 문자열의 최대 길이(null 문자포함)
const int MAX_ID_LENGTH = 21;

// char* 를 사용한 Stack 클래스(List 6) 템플릿 전문화
template<> 
class Stack
{
public:
  explicit Stack( int size )
  {
    m_Size = size;

    m_ppData = new char *[m_Size];
    for( int i = 0; i < m_Size; ++i )
    {
      m_ppData[i] = new char[MAX_ID_LENGTH];
    }

    Clear();
  }

  ~Stack()
  {
    for( int i = 0; i < m_Size; ++i )
    {
      delete[] m_ppData[i];
    }

    delete[] m_ppData;
  }

  // 초기화한다.
  void Clear()
  {
    m_Count = 0;
  }

  // 스택에 저장된 개수
  int Count()
  {
    return m_Count;
  }

  // 저장된 데이터가 없는가?
  bool IsEmpty()
  {
    return 0 == m_Count ? true : false;
  }

  // 데이터를 담을 수 있는 최대 개수
  int GetStackSize()
  {
    return m_Size;
  }

  // 데이터를 저장한다.
  bool push( char* pID )
  {
    // 저장할 수 있는 개수를 넘는지 조사한다.
    if( m_Count >= m_Size )
    {
      return false;
    }

    // 저장 후 개수를 하나 늘린다.
    strncpy_s( m_ppData[m_Count], MAX_ID_LENGTH, pID, MAX_ID_LENGTH - 1);
    m_ppData[m_Count][MAX_ID_LENGTH - 1] = '\0';

    ++m_Count;

    return true; 
  }

  // 스택에서 빼낸다.
  char* pop()
  {
    // 저장된 것이 없다면 0을 반환한다.
    if( m_Count  < 1 )
    {
      return 0;
    }

    // 개수를 하나 감소 후 반환한다.
    --m_Count;
    return m_ppData[ m_Count ];
  }

private:
  char** m_ppData;
  int  m_Count;

  int m_Size;
};

#include 

using namespace std;

void main()
{
  Stack kStack1(64);
  cout << "스택의 크기는? " << kStack1.GetStackSize() << endl;
  kStack1.push( 10 );
  kStack1.push( 11 );
  kStack1.push( 12 );

  int Count1 = kStack1.Count();
  for( int i = 0; i < Count1; ++i )
  {
    cout << "유저의 레벨 변화 -> " << kStack1.pop() << endl;
  }

  cout << endl;

  char GameID1[MAX_ID_LENGTH] = "NiceChoi";
  char GameID2[MAX_ID_LENGTH] = "SuperMan";
  char GameID3[MAX_ID_LENGTH] = "Attom";

  // Stack 클래스 템플릿의 char*  전문화 버전을 생성한다.
  Stack kStack2(64);
  kStack2.push(GameID1);
  kStack2.push(GameID2);
  kStack2.push(GameID3);

  int Count2 = kStack2.Count();
  for(int i = 0; i < Count2; ++i)
  {
    cout << "같이 게임을 한유저의 ID -> " << kStack2.pop() << endl;
  }
}

실행 결과 

그림8

클래스 템플릿 부분 전문화

클래스 템플릿은 템플릿 파라메터 중 일부를 구체적인 형(type)을 사용, 또는 템플릿 파라메터를 포인터나 참조를 사용하여 부분 전문화를 할 수 있습니다. 

- 구체적인 형 사용에 의한 부분 전문화

template< typename T1, typename T2 > class Test { …. };

의 T2를 float로 구체화 하여 부분 전문화를 하면 다음과 같습니다.

template< typename T1 > class Test { ….. };

코드는 다음과 같습니다. 

template< typename T1, typename T2 >
class Test
{
public:
  T1 Add( T1 a, T2 b )
  {
    cout << "일반 템플릿을 사용했습니다." << endl;
    return a;
  }
};

// T2를 float로 구체화한 Test의 부분 전문화 템플릿
template< typename T1 > 
class Test
{
public:
  T1 Add( T1 a, float b )
  {
    cout << "부분 전문화 템플릿을 사용했습니다." << endl;
    return a;
  }
};

#include 

using namespace std;

void main()
{
  Test test1;
  test1.Add( 2, 3 );

  Test test2;
  test2.Add( 2, 5.8f );
}

그림9 

위의 예에서는 템플릿 파라메터 2개 중 일부를 구체화하여 부분 전문화를 했지만 당연하지만 2개 이상도 가능합니다.

template< typename T1, typename T2, typename T3 > class Test { …. };

의 부분 전문화 템플릿은

template< typename T1, typename T2 > class Test { ….. };

- 포인터의 부분 전문화

template< typename T > class TestP { …. };

의 T의 T* 부분 전문화를 하는 다음과 같습니다.

template< typename T > class TestP {  …… };

코드는 다음과 같습니다. 

template< typename T > 
class TestP
{
public:
  void Add()
  {
    cout << "일반 템플릿을 사용했습니다." << endl;
  }
};

// T를 T*로 부분 전문화
template< typename T > 
class TestP
{
public:
  void Add()
  {
    cout << "포인터를 사용한 부분 전문화 템플릿을 사용했습니다." << endl;
  }
};

#include 

using namespace std;

void main()
{
  TestP test1;
  test1.Add();

  TestP test2;
  test2.Add();
}

실행 결과 

그림10

싱글톤 템플릿 클래스

클래스 상속을 할 때 템플릿 클래스를 상속 받음으로 상속 받는 클래스의 기능을 확장할 수 있습니다. 

저의 경우 현업에서 클래스 템플릿을 가장 많이 사용하는 경우가 클래스 템플릿을 사용한 싱글톤 클래스 템플릿을 사용하는 것입니다. 

어떠한 객체가 꼭 하나만 있어야 되는 경우 싱글톤으로 정의한 클래스 템플릿을 상속 받도록 합니다.

싱글톤은 싱글톤 패턴을 말하는 것으로 어떤 클래스의 인스턴스가 꼭 하나만 생성되도록 하며, 전역적인 접근이 가능하도록 합니다. 어떤 클래스를 전역으로 사용하는 경우 복수개의 인스턴스가 생성되지 않도록 싱글톤 패턴으로 생성하는 것을 권장합니다.

사용하는 방법은 베이스 클래스를 템플릿을 사용하여 만듭니다. 그리고 이것을 상속 받는 클래스에서 베이스 클래스의 템플릿 파라메터에 해당 클래스를 사용합니다. 즉 싱글톤 클래스 템플릿은 이것을 상속 받는 클래스를 싱글톤으로 만들어줍니다. 

위에서 설명한 클래스 템플릿에 대하여 이해를 하셨다면 의 코드를 보면 이해를 할 수 있으리라 생각합니다. 싱글톤 클래스 템플릿은 직접 생성을 하지 않으므로 주 멤버들을 static로 만들어줍니다. 그리고 생성자를 통해서 _Singleton를 생성하지 않고 GetSingleton()을 통해서만 생성하도록 합니다. 

#include 
using namespace std;


// 파라메터 T를 싱글톤이 되도록 정의 합니다.
template <typename T>
class MySingleton
{
public:
    MySingleton() {}
    virtual ~MySingleton() {}

    // 이 멤버를 통해서만 생성이 가능합니다.
    static T* GetSingleton()
    {
        // 아직 생성이 되어 있지 않으면 생성한다.
        if( NULL == _Singleton ) {
            _Singleton = new T; 
        }

       return ( _Singleton );
    }

    static void Release()
    {
        delete _Singleton;
        _Singleton = NULL;
    }

private:
    static T* _Singleton;
};

template <typename T> T* MySingleton ::_Singleton = NULL;

// 싱글톤 클래스 템플릿을 상속 받으면서 파라메터에 본 클래스를 넘깁니다.
class MyObject : public MySingleton
{
public:
MyObject() : _nValue(10) {}

void SetValue( int Value ) { _nValue = Value;}  
int GetValue() { return _nValue; }

private :
int _nValue;
};

void main()
{
   MyObject* MyObj1 = MyObject::GetSingleton();

   cout << MyObj1->GetValue() << endl;

   // MyObj2는 Myobj1과 동일한 객체입니다.
   MyObject* MyObj2 = MyObject::GetSingleton();
   MyObj2->SetValue(20);

   cout << MyObj1->GetValue() << endl;
   cout << MyObj2->GetValue() << endl;
}

클래스 템플릿 코딩 스타일 개선

위에서 예제로 구현한 다양한 클래스 템플릿의 코딩 스타일은 클래스 선언 안에서 각 멤버들의 정의를 구현하고 있습니다. 클래스의 코드 길이가 크지 않은 경우는 코드를 보는데 불편하지 않지만 코드 길이가 길어지는 경우 클래스의 전체적인 윤곽을 바로 알아보기가 쉽지 않습니다. 

긴 코드를 가지는 클래스 템플릿의 경우는 클래스의 선언과 정의를 분리하는 것이 좋습니다. 위에서 예제로 나온 클래스 템플릿 중 의 Stack 클래스 템플릿을 선언과 정의를 분리하면 아래와 같습니다. 

template<typename T> 
class Stack
{
public:
  explicit Stack( int size );

  ~Stack();

  // 초기화 한다.
  void Clear();

  // 스택에 저장된 개수
  int Count();

  // 저장된 데이터가 없는가?
  bool IsEmpty();

  // 데이터를 담을 수 있는 최대 개수
  int GetStackSize();

  // 데이터를 저장한다.
  bool push( T data );

  // 스택에서 빼낸다.
  T pop();

private:
  T*  m_aData;
  int  m_Count;

  int m_Size;
};

template < typename T > 
Stack::Stack( int size )
{
  m_Size = size;
  m_aData = new T[m_Size];

  Clear();
}

template < typename T > 
Stack::~Stack()
{
  delete[] m_aData;
}

template < typename T > 
void Stack::Clear()
{
  m_Count = 0;
}

template < typename T > 
int Stack::Count()
{
  return m_Count;
}

template < typename T >
bool Stack::IsEmpty()
{
  return 0 == m_Count ? true : false;
}

template < typename T > 
int Stack::GetStackSize()
{
  return m_Size;
}

template < typename T > 
bool Stack::push( T data )
{
  // 저장할 수 있는 개수를 넘는지 조사한다.
  if( m_Count >= m_Size )
  {
    return false;
  }

  // 저장 후 개수를 하나 늘린다.
  m_aData[ m_Count ] = data;
  ++m_Count;

  return true; 
}

template < typename T > 
T Stack::pop()
{
  // 저장된 것이 없다면 0을 반환한다.
  if( m_Count  < 1 )
  {
    return 0;
  }

  // 개수를 하나 감소 후 반환한다.
  --m_Count;
  return m_aData[ m_Count ];
}

의 코드를 보면 알듯이 클래스 안에 정의를 했던 것과의 차이점은 클래스 멤버 정의를 할 때 템플릿 선언하고 클래스 이름에 템플릿 파라메터를 적어 줍니다.

클래스 선언과 정의를 각각 다른 파일에 하려면

일반적인 클래스의 경우 크기가 작은 경우를 제외하면 클래스의 선언과 정의를 서로 다른 파일에 합니다. 

클래스 템플릿의 경우는 일반적인 방법으로는 그렇게 할 수가 없습니다. 클래스 멤버 정의를 선언과 다른 파일에 하려면 멤버 정의를 할 때 'export'라는 키워드를 사용합니다. 의 GetStackSize()에 export를 사용하면 아래와 같이 됩니다.

template < typename T > 
export int Stack::GetStackSize()
{
  return m_Size;
}

그러나 export라는 키워드를 사용하면 컴파일 에러가 발생합니다. 이유는 현재 대부분의 C++ 컴파일러에서는 export라는 키워드를 지원하지 않습니다. export를 아직 지원하지 못하는 이유는 이것을 지원하기 위해 필요로 하는 노력은 컴파일러를을 새로 만들 정도의 노력을 필요로 할 정도로 어렵다고 합니다. 현재까지도 대부분의 컴파일러 개발자들은 구현 계획을 세우지도 않고 있으며 일부에서는 구현에 반대하는 의견도 있다고 합니다. 

그럼 클래스 템플릿의 선언과 정의를 서로 다른 파일에 할 수 있는 방법은 없을까요? 약간 편법을 사용하면 가능합니다. 

inline이라는 의미를 가지고 있는 '.inl' 확장자 파일에 클래스 구현하고 이 .inl 파일을 헤더 파일에서 포함합니다. (참고로 .inl 파일을 사용하는 것은 일반적인 방식은 아니고 일부 라이브러리나 상용 3D 엔진에서 간혹 사용하는 것을 볼 수 있습니다). 

의 Stack 클래스 템플릿의 선언과 정의를 다른 파일로 하는 예의 일부를 아래에 보여드리겠습니다.

// stack.h 파일
template<typename T> 
class Stack
{
public:

  // 초기화 한다.
  void Clear();
 
};

#include "stack.inl"

// stack.inl 파일

template < typename T > 
void Stack::Clear()
{
  m_Count = 0;
}

이것으로 클래스 템플릿에 대한 설명은 다 한 것 같습니다. 함수 템플릿에 대한 글을 이미 보셨으면 템플릿에 대한 어느 정도 이해를 가지고 있을 테니 어렵지 않게 이해를 할 수 있으리라 생각합니다만 저의 부족한 글 때문에 어렵지 않았을까라는 걱정도 조금합니다. 

글을 그냥 보고 넘기지 마시고 직접 코딩을 해 보시기를 권장합니다. 본문에 나오는 예제들은 모두 코드 길이가 짧은 것이라서 직접 코딩을 하더라도 긴 시간은 걸리지 않을 것입니다. 

다음회부터는 본격적으로 STL에 대한 설명에 들어갑니다. 전 회에서 이야기 했듯이 STL은 템플릿으로 만들어진 것입니다. 아직 템플릿의 유용성을 느끼지 못한 분들은 STL에 대해서 알게 되시면 템플릿의 뛰어남을 알게 되리라 생각합니다.


출처 : http://www.hanb.co.kr/

Trackback 0 And Comment 0

[펌] About STL : C++ STL 프로그래밍(2-1)

|

제공: 한빛 네트워크
저자: 최흥배
이전기사:

함수 템플릿

두 값을 비교하는 함수를 만들어야 됩니다.

앞서 제가 하는 일을 이야기했습니다. 네, 온라인 게임을 만들고 있습니다. 게임에서 구현해야 되는 것에는 캐릭터 간에 HP를 비교하는 것이 필요합니다. 그래서 두 개의 int 타입을 비교하는 Max라는 이름의 함수를 하나 만들었습니다.

int Max( int a, int b );

일을 다 끝낸 후 다음 기획서를 보니 캐릭터와 NPC가 전투를 하는 것을 구현해야 되는데 여기에는 경험치를 비교하는 기능이 필요합니다. 구현해야 되는 것은 위에서 만든 Max 함수와 같습니다. 그래서 그것을 사용하였습니다. 

< List 1 >

#include 
using namespace std;

int Max( int a, int b )
{
  return a > b ? a : b;
}

void main()
{
  int Char1_HP = 300;
  int Char2_HP = 400;
  int MaxCharHP = Max( Char1_HP, Char2_HP );
  cout << "HP 중 가장 큰 값은" << MaxCharHP << "입니다." << endl << endl;
  
  float Char1_Exp = 250.0f;
  float Char2_Exp = 250.57f;
  float MaxCharExp = Max( Char1_Exp, Char2_Exp );
  cout << "경험치 중 가장 큰 값은" << MaxCharExp << "입니다." << endl << endl;
}

앗, 체력(HP)을 저장하는 변수의 타입은 int인데, 경험치를 저장하는 변수의 타입은 int가 아닌 float 타입니다. 

그림1 

당연하게 경험치를 비교하는 부분은 버그가 있습니다.
앞에 만들었던 Max와는 다르게 비교하는 변수의 타입이 float인 것이 필요하여 새로 만들었습니다. 

< List 2 >

float Max( float a, float b )
{
  return a > b ? a : b;
}

함수 오버로딩에 의해 경험치를 비교할 때는 int 타입의 Max가 아닌 의 float 타입을 비교하는 Max가 호출되어 버그가 사라지게 되었습니다. 

이제 경험치 비교는 끝나서 다음 기획서에 있는 것을 구현해야 합니다. 이번에는 돈을 비교하는 것이 있습니다. 그런데 돈을 저장하는 변수의 타입은 __int64입니다. __int64는 비주얼 C++에서만 사용할 수 있는 64비트 정수 타입입니다. __int64 타입을 비교하는 것은 앞에서 만든 int 타입의 Max나 float 타입의 Max로 할 수 없습니다. 함수에서 사용하는 변수의 타입만 다를 뿐 똑같은 것을 또 만들어야 됩니다.

__int64 Max(__int64 a, __int64 b )
{
  return a > b ? a : b;
}

현재까지만 하더라도 이미 똑같은 로직으로 구현된 함수를 3개나 만들었는데, 게임에서 사용하는 캐릭터의 정보는 HP, 경험치, 돈 이외에도 더 많습니다. 저는 앞으로 Max 함수를 몇 개 더 만들어야 할지 모릅니다. Max 함수의 구현을 고쳐야 한다면 모든 Max 함수를 찾아야 합니다. 함수 오버로딩은 문제를 해결하지만, 코드가 커지고 유지보수는 어렵게 만듭니다. 

프로그래밍에서 유지보수는 아주 중요합니다. 왜냐하면, 프로그래밍은 언제나 변경이 가해지기 때문입니다. 유지보수를 편하게 하는 가장 간단한 방법은 유지보수 할 것을 줄이는 것입니다.

Max 함수를 하나로 만들고 싶습니다. 어떻게 해야 될까요?

앗, 혹시 모른다고요? 제가 이 앞에 템플릿에 대해 설명을 할 때 이런 말을 하지 않았나요?
'템플릿을 사용하면 타입에 제약을 받지 않는 로직을 기술 할 수 있습니다'
네, 템플릿을 사용하면 됩니다. 

함수 템플릿 Max를 만들자 

아래의 코드는 템플릿을 사용하여 Max 함수를 구현 한 것입니다. 

< List 3 >

#include 
using namespace std;

// 템플릿으로 만든 값을 비교하는 Max 함수
template 
T Max(T a, T b )
{
  return a > b ? a : b;
}


void main()
{
  int Char1_HP = 300;
  int Char2_HP = 400;
  int MaxCharHP = Max( Char1_HP, Char2_HP );
  cout << "HP 중 가장 큰 값은" << MaxCharHP << "입니다." << endl << endl;
  
  float Char1_Exp = 250.0f;
  float Char2_Exp = 250.57f;
  float MaxCharExp = Max( Char1_Exp, Char2_Exp );
  cout << "경험치 중 가장 큰 값은" << MaxCharExp << "입니다." << endl << endl;
}

실행한 결과는 다음과 같습니다. 

그림2 

네 이번에는 경험치 비교가 정확하게 이루어졌습니다.
템플릿을 사용하게 되어 이제는 불필요한 Max 함수를 만들지 않아도 됩니다.. 

List 3 코드에서 template으로 만든 함수를 '함수 템플릿'이라고 합니다. 

함수 템플릿을 정의하는 방법은 아래와 같습니다. 

그림3 

템플릿을 사용하면 Generic Programming을 할 수 있다
라고 앞서 이야기 했는데 위의 Max 함수 템플릿을 보고 좀 이해를 하셨나요?
혹시나 해서 그림으로 조금만 더 설명하겠습니다. 

그림4 

암소를 총칭(Generic)화하면 동물이라고 할 수 있습니다.
Max 함수 템플릿에서는 함수의 반환 값과 함수 인자인 a 와 b의 타입인 int 나 float를 T로 Generic화 하였습니다. 

그림5

함수 템플릿과 컴파일

하나의 Max 함수 템플릿을 만들었는데 어떻게 int 타입의 Max와 float 타입의 Max를 사용할 수 있을까요? 비밀은 컴파일하는 과정에 있습니다. 컴파일할 때 템플릿으로 만든 것은 템플릿으로 만든 함수를 호출하는 부분에서 평가합니다. 가상 함수처럼 실행시간에 평가하는 것이 아닙니다. 

컴파일을 할 때(compile time) 함수 템플릿을 평가하므로 프로그램의 성능에 해가 되는 것은 없습니다. 

컴파일할 때 평가를 하면서 문법적으로 에러가 없는지 검사합니다. 만약 에러가 있다면 컴파일 에러를 출력합니다. 에러가 없다면 관련 코드를 내부적으로 생성합니다. 

List 3을 예로 들면, void main()의 다음 부분을 컴파일하면 Max를 호출할 때 사용한 인자의 변수의 타입이 Max에서 정의 한 문법에 틀리지 않는지 체크한 후 int 타입을 사용하는 Max 함수의 코드를 만듭니다.

int MaxCharHP = Max( Char1_HP, Char2_HP );

이후 다음 부분에서 Max를 만나면 이번에도 위의 int 때와 같이 문법 체크를 한 후 에러가 없다면 float를 사용하는 Max 함수 코드를 만듭니다.

float MaxCharExp = Max( Char1_Exp, Char2_Exp );

Max가 만들어지는 과정을 나타내면 아래와 같습니다. 모든 타입에 대해 Max 함수를 만드는 것은 아닙니다. 코드에서 사용한 타입에 대해서만 Max 함수가 만들어집니다. 

그림6 

참고로 이렇게 만들어지는 코드는 소스 코드에 만들어지는 것이 아니고 프로그램의 코드 영역에 만들어집니다. 컴파일 타임에 함수 템플릿을 평가하고 관련 코드를 만들기 때문에 템플릿을 많이 사용하면 컴파일 시간이 길어질 수 있으며, 각 타입에 맞는 코드를 만들어내므로 실행 파일의 크기도 커질 수 있습니다.

Max 함수 템플릿에 개선점이 없을까요?

힌트를 드린다면 Max의 두 인자 값은 함수 내부에서 변경되지 않습니다. 그리고 인자의 타입은 C++의 기본형뿐만이 아닌 크기가 큰 타입을 사용할 수도 있습니다. 

생각나셨나요? C++ 기초 공부를 차근차근 쌓아 올린 분이라면 알아차렸으리라 생각합니다. 

정답은 Max 함수 템플릿을 만들 때 템플릿의 인자에 const와 참조를 사용하는 것입니다. Max 함수는 함수의 내부에서 함수의 인자를 변경하지 않습니다. 그러니 함수에 const를 사용하여 내부에서 변경하는 것을 명시적으로 막고 Max 함수를 사용하는 사람에게 알리는 역할을 합니다. 

C++에서 함수 인자의 전달을 빠르게 하는 방법은 참조로 전달하는 것입니다. 위의 Max 함수는 int나 float 같은 크기가 작은 타입을 사용하였기 때문에 참조로 전달하는 것이 큰 의미는 없지만, 만약 구조체나 클래스로 만들어진 크기가 큰 변수를 사용할 때는 참조로 전달하는 것이 훨씬 빠릅니다. 앞에 만든 Max 함수 템플릿을 const와 참조를 사용하는 것으로 바꾸어 보았습니다. 

< List 4 >

template 
const T& Max(const T& a, const T& b )
{
  return a > b ? a : b;
}

class T 라는 것을 본적이 있나요?

함수 템플릿을 만들 때 'typename'을 사용했습니다. 그러나 좀 오래된 C++ 책에서 템플릿에 대한 글을 본 적이 있는 분은 'class'를 사용한 것도 본 적이 있을 것입니다. 

< List 5 >

template 
const T& Max(const T& a, const T& b )
{
  return a > b ? a : b;
}

typename과 class는 기능적으로 다른 것이 아닙니다. 템플릿이 표준이 되기 전에는 'class'를 사용했습니다. 그래서 표준화 이전이나 조금 지난 뒤에 나온 책에서는 'class'로 표기했습니다. 그리고 예전에 만들어진 C++ 컴파일러도 템플릿 인자 선언으로 'class'만 지원했습니다. 만약, C++ 표준화 전후에 만들어진 컴파일러에서는 'class'를 사용해야 합니다. 

현재의 컴파일러에서는 'class', 'typename' 둘 다 지원합니다. 하지만, 'class'보다 프로그래머에게 '타입'을 추상화한 것이라는 의미 전달을 명확하게 하는 typename을 사용합니다. class만 지원하는 오래된 C++ 컴파일러에서 컴파일 해야 하는 것이 아니면 꼭 'typename'을 사용하세요.

이제 Max 함수 템플릿에는 문제가 없을까요?

위에서 Max 함수 템플릿에 대해서 const와 참조로 개선을 했는데 이제 문제가 없을까요? 

그럼 아래의 코드는 문제가 없이 컴파일이 잘 될까요? 

< List 6 >

// List3의 Max 함수 템플릿을 사용합니다.
void main()
{
  int Char1_MP = 300;
  double Char1_SP = 400.25;
  double MaxValue1 = Max( Char1_MP, Char1_SP );
  cout << "MP와 SP 중 가장 큰값은" << MaxValue1 << "입니다." << endl << endl;
  
  double MaxValue2 = Max( Char1_SP, Char1_MP );
  cout << "MP와 SP 중 가장 큰값은" << MaxValue2 << "입니다." << endl << endl;
}

List 6을 컴파일 하면 다음과 같은 에러가 출력됩니다.

max.cpp
max.cpp(16) : error C2782: 'const T &Max(const T &,const T &)' 
        : 템플릿 매개 변수 'T'이(가) 모호합니다.
        max.cpp(6) : 'Max' 선언을 참조하십시오.
        'double'일 수 있습니다.
        또는      'int'
max.cpp(19) : error C2782: 'const T &Max(const T &,const T &)' 
        : 템플릿 매개 변수 'T'이(가) 모호합니다.
        max.cpp(6) : 'Max' 선언을 참조하십시오.
        'int'일 수 있습니다.
        또는      'double'

이유는 컴파일러는 사람이 아니어서 서로 다른 타입의 인자가 들어오면 템플릿의 파라메터 T를 사용한 함수의 인자 a와 b의 타입을 int로 해야 할지, double로 해야 할지 판단할 수가 없기 때문입니다. 이 문제는 어떻게 해결 해야 될까요?

typename을 하나가 아닌 복수 개 사용하면 됩니다.

위의 문제는 Max 함수를 정의할 때 typename을 하나만 사용해서 타입을 하나만 선언했습니다. 이제 typename을 여러 개 사용하면 위의 문제를 풀 수 있습니다. 

< List 7 >

template 
const T1& Max(const T1& a, const T2& b )
{
  return a > b ? a : b;
}

List 7의 함수 템플릿을 사용하면 Max 함수의 인자 타입을 int와 double 혹은 double과 int 타입을 사용해도 컴파일이 잘 됩니다. 그럼 제대로 실행 되는지 실행을 해 볼까요? 

그림7 

앗, 실행 결과에 오류가 있습니다.

int Char1_MP = 300;
double Char1_SP = 400.25;
double MaxValue1 = Max( Char1_MP, Char1_SP );

이 코드는 300과 400.25를 비교합니다. 결과는 400.25가 나와야 하는데 400이 나와버렸습니다. 

이유는 List 7의 함수 템플릿의 반환 값으로 T1을 선언했기 때문에 int 타입과 double 타입을 순서대로 함수 인자에 사용하면 반환 값의 타입이 int형으로 되어 버리기 때문입니다. 이렇게 서로 다른 타입을 사용하는 경우에는 반환 값을 아주 조심해야 합니다. 그리고 위의 예에서는 함수 템플릿의 파라메터로 typename을 2개 사용했지만 그 이상도 사용할 수 있습니다. 

위의 Max 함수 템플릿 만족스럽나요?
저는 왠지 아직도 좀 불 만족스럽습니다.

Max( int, double);

실수를 하면 찾기 힘든 버그가 발생할 확률이 높습니다.
이것을 어떻게 풀어야 될까요?

함수 템플릿의 전문화 라는 것이 있습니다.

Max(int, double)을 사용하면 Max 함수 템플릿이 아닌 이것에 맞는, 특별하게 만든 함수를 사용하도록 합니다. 함수 템플릿의 전문화(Specialization)라는 특별한 상황에 맞는 함수를 만들면 함수 오버로드와 같이 컴파일러가 상황에 맞는 함수를 선택하도록 합니다. 

< List 8 >

#include 
using namespace std;

// 템플릿으로만든값을비교하는Max 함수
template 
const T1& Max(const T1& a, const T2& b )
{
  cout << "Max(const T& a, const T& b) 템플릿 버전 사용" << endl;
  return a > b ? a : b;
}

// 전문화시킨Max 함수
template <> 
const double& Max(const double& a, const double& b)
{
  cout << "Max(const double& a, const double& b) 전문화 버전 사용" << endl;
  return a > b ? a : b;
}

void main()
{
  double Char1_MP = 300;
  double Char1_SP = 400.25;
  double MaxValue1 = Max( Char1_MP, Char1_SP );
  cout << "MP와 SP 중 가장 큰 값은" << MaxValue1 << "입니다." << endl << endl;
  
  int Char2_MP = 300;
  double Char2_SP = 400.25; 
  double MaxValue2 = Max( Char2_MP, Char2_SP );
  cout << "MP와 SP 중 가장 큰 값은" << MaxValue2 << "입니다." << endl << endl;
}

위 코드를 실행한 결과는 아래와 같습니다. 

그림8 

컴파일러는 프로그래머의 생각을 완전히 이해하지는 않습니다. 그래서 컴파일러가 어떠한 것을 선택할지 이해하고 있어야 됩니다. List 8은 double에 전문화 된 Max 함수를 만든 예입니다. 

[질문] Max(10.1, 20.4)를 호출한다면 Max(T, T)가 호출 될까요? 아님 Max(double, double)가 호출 될까요? 

답을 빨리 알고 싶을 테니 뜸 들이지 않고 결과를 바로 보여드리겠습니다. 

그림9 

전문화 버전이 호출 되었습니다. 이유는 호출 순서에 규칙이 있기 때문입니다(최선에서 최악으로). 호출 순서는 다음과 같습니다.

  1. 전문화된 함수와 맞는지 검사한다.
  2. 템플릿 함수와 맞는지 검사한다.
  3. 일반 함수와 맞는지 검사한다.

위의 순서를 잘 기억하고 전문화 함수를 만들어야 합니다. 잘못하면 찾기 힘든 버그를 만들 수가 있습니다. 이제 함수 템플릿에 대한 이야기는 거의 다 끝난 것 같습니다. 

아... 하나 더 있습니다. 

이때까지 한 것들은 타입만을 템플릿 파라메터로 사용했는데 꼭 타입만 함수 템플릿에 사용할 수 있는 것은 아닙니다.

난-타입(non-type) 함수 템플릿

온라인 게임에서는 특정한 이벤트가 있을 때는 캐릭터의 HP, 경험치, 돈을 이벤트 기념으로 주는 경우가 있습니다. HP와 경험치, 돈의 타입은 다르지만 추가 되는 값은 int 상수로 정해져 있습니다. 위와 같이 타입은 다르지만 상수를 더 한 값을 얻는 함수를 만들려면 어떻게 해야 될까요? 

이런 문제도 함수 템플릿으로 해결할 수 있습니다. 

함수 템플릿의 파라메터로 꼭 typename만이 아닌 값을 파라메터로 사용할 수도 있습니다. 

아래의 코드는 캐릭터의 HP, 경험치, 돈을 이벤트에서 정해진 값만큼 더 해주는 것을 보여줍니다. 

< List 9 >

#include <iostream>
using namespace std;

// 지정된 값만큼 더해준다.
template <typename T, int VAL>
T AddValue( T const& CurValue)
{
  return CurValue + VAL;
}

const int EVENT_ADD_HP_VALUE  = 50;    // 이벤트에 의해 추가 될 HP 값
const int EVENT_ADD_EXP_VALUE  = 30;    // 이벤트에 의해 추가 될 경험치
const int EVENT_ADD_MONEY_VALUE  = 10000;    // 이벤트에 의해 추가 될 돈

void main()
{
  int Char_HP = 250;
  cout << Char_HP <<"에서 이벤트에 의해" << AddValue<int, 
       EVENT_ADD_HP_VALUE>(Char_HP) << " 로 변경" <<endl;

  float Char_EXP = 378.89f;
  cout << Char_EXP <<"에서 이벤트에 의해" << AddValue<float, 
       EVENT_ADD_EXP_VALUE>(Char_EXP) << " 로 변경" <<endl;

  __int64 Char_MONEY = 34567890;
  cout << Char_MONEY <<"에서 이벤트에 의해" << AddValue<__int64,   
       EVENT_ADD_MONEY_VALUE>(Char_MONEY) << " 로 변경" <<endl;
}

실행 결과는 다음과 같습니다. 

그림10 

앞에서 사용했던 함수 템플릿 사용 방법과 좀 틀려서 생소할 수도 있겠네요. 

제가 위에 예로든 것은 난 타입 함수 템플릿을 사용해야 되는 당위성이 좀 떨어질 수도 있다고 생각합니다만 설명을 위해서 간단하게 예를 보여주기 위해서 라고 변명해 봅니다. ^^;; 

난 타입을 사용하는 템플릿은 다음 회에 이야기 할 클래스 템플릿에서도 또 다시 이야기 할 예정이니 잘 기억하고 있으시기를 바랍니다. 또 난 타입을 잘 사용하면 템플릿 메타 프로그래밍을 할 때 큰 도움이 됩니다. 템플릿 메타 프로그래밍에 대해서는 다음에 설명해 드리겠습니다.


출처 : http://www.hanb.co.kr/

Trackback 0 And Comment 0

[펌] About STL : C++ STL 프로그래밍(1)

|

STL이 무엇인지 알고 계십니까?

C++를 주 프로그래밍 언어로 사용하고 계신 분들은 알고 있으리라 생각합니다. STL은 C++ 언어의 '표준 템플릿 라이브러리 (Standard Template Library) '의 약자입니다. 

STL을 간단하게 말하자면 일반적으로 많이 사용될 수 있는 자료구조와 알고리즘 모음 라이브러리가 말할 수 있습니다. 

STL은 C++ 언어가 처음 만들어질 때부터 있었던 것이 아니고 1998년에 C++ 표준이 정해지기 전인 1993년 말 무렵에 Alex Stepanov가 C++ 언어의 창시자인 Bjarne Stroustrup에게 보여준 후 준비 기간을 걸쳐서 1994년에 표준 위원회에 초안이 통과됩니다. 

참고로 C++ 표준은 1989년에 시작되어 1998년 9월에 마무리되었습니다.

STL은 어떻게 만들었을까요?

답은 위에 STL의 실제 이름에 포함 되어 있습니다. 좀 더 힌트를 드린다면 세 개의 단어 중 중간에 있는 것입니다. 

네, 템플릿(Template)으로 만들어 진 것입니다. 

STL을 이해하려면 STL을 만들 수 있게 해준 C++의 템플릿에 대한 이해는 필수입니다. 또, 템플릿은 C++를 더욱 강력하게 사용하는 데 꼭 필요합니다.

C++ 언어를 공부한 사람은 템플릿에 대해 잘 알고 있을까요?

예전에 C++을 잠시 공부했던 분이나 근래에 공부하고 있는 분 중 아직 C++ 책을 한, 두 권 본 정도라면 템플릿이라는 단어가 생소할 수 있습니다. 

위에 언급했듯이 템플릿은 C++이 세상에 나오면서 같이 나온 것이 아니고 1994년 무렵에야 세상에 조금씩 소개되다가 1998년에 C++ 표준이 정립되고서야 C++ 언어의 한 부분으로서 인정되었습니다. 

1994년까지는 템플릿을 지원하는 C++ 컴파일러가 없었고, MS의 C++ 툴로 유명한 Visual C++도 버전 6에서는 템플릿을 완벽하게 지원하지 못했으면 Visual Studio .NET 2000 에서부터 제대로 지원을 하게 되었습니다(아직도 템플릿 기능을 100% 완벽하게 지원하지는 못합니다.). 2000년도 이전에 나온 C++ 입문서를 보면 템플릿에 대하여 빠뜨린 것이 꽤 많습니다. 

근래 나오는 입문서에서도 빠져 있기도 하며 또 보통 C++ 입문서에서는 가장 뒷부분에 나오다 보니 공부를 하다가 중간에 포기하는 분들은 클래스라는 것은 알아도 템플릿은 잘 모릅니다. 

개인적으로 C 언어를 생각하면 포인터가 떠오르고, C++ 언어를 생각하면 클래스와 템플릿이 떠올라집니다. 이유는 C 언어나 C++ 언어를 배울 때 정확하게 이해하기 가장 어려웠던 것이고 제가 배웠던 다른 언어들에 비해 크게 달랐던 것이기 때문입니다. 

포인터는 처음 배울 때 문법적인 사용 방법이 잘 이해가 안 돼서 어려웠지만, 클래스나 템플릿은 문법적인 사용 방법이 어려운 것이 아니고 이것들이 프로그램 설계와 관계된 것들이라 사상 부분을 이해하기 어려웠습니다.

객체 지향 프로그래밍(OOP) C++

C++ 언어를 소개할 때 가장 먼저 이야기하는 것이 객체지향이라는 것입니다. 현대 언어들은 거의 다 OOP 베이스의 언어이던가 지원을 하고 있습니다. 

C 언어와 C++ 언어는 이름이 비슷하듯이 비슷한 부분이 많습니다. C 언어로 프로그래밍할 때는 절차 지향적 프로그래밍을 하게 됩니다. C++도 절차 지향 프로그래밍을 할 수 있습니다. 그러나 제대로 된 C++ 프로그래밍을 하려면 객체 지향 프로그래밍을 해야 합니다. 보통 C 언어를 배운 후 바로 이어서 C++를 배울 때는 객체 지향 프로그래밍(Object-Oriented Programming)에 대한 이해가 부족합니다. 그래서 C 언어로 프로그래밍할 때와 같은 절차 지향 프로그래밍을 하여 이른바 'C++를 가장한 C 프로그래밍'을 한다는 소리를 듣기도 합니다. C++ 언어로 객체 지향 프로그래밍을 할 수 있는 것은 C 언어에는 없는 클래스가 있기 때문입니다. 

[질문] C++로 할 수 있는 프로그래밍 스타일은 절차적 프로그래밍, 객체 지향 프로그래밍만 있을까요?
[답] 아니오. Generic Programming 도 가능합니다.

Generic Programming 이라는 것을 들어 보셨나요?

제가 프로그래밍을 배울 때는 일반적으로 C++ 언어를 배우기 전에 C 언어를 공부했습니다. C 언어를 처음 공부했던 시기가 제 기억으로는 1994년쯤입니다. 그 당시의 다른 초보 프로그래머들처럼 포인터의 벽에 부딪혀 좌절하고, 도망(?)가서 3D Studio라는 그래픽 툴을 공부하다가 제가 할 것이 아니라는 생각에 포기하고, 1995년에 다시 C 언어를 공부하였고 이후 바로 C++ 언어를 공부했습니다. 

이때도 OOP라는 단어는 무척 자주 들었고 C++로 프로그래밍을 잘한다는 것은 OOP를 잘한다는 것과 같은 뜻이었습니다. 

대학을 다닐 때부터 제 용돈의 많은 부분은 프로그래밍 책을 사는 데 사용되었습니다. 그중에서 저는 C++ 언어 책을 꽤 많이 구매하여 보았습니다(다만, 제대로 이해한 책은 별로 없었습니다. -_-;;; ). 

책에서는 언제나 OOP 단어는 무수히 많이 보았지만, Generic Programming이라는 단어를 그 당시에 본 기억이 없습니다. 제가 Generic Programming이라는 단어를 알게 된 것은 2001년 무렵입니다. C++ 언어를 공부한 지 거의 6년이 되어서야 알게 되었습니다. 

아마 지금 공부하시는 분들도 Generic Programming이라는 단어는 좀 생소할 것입니다. 

Generic Programming은 한국에서는 보통 '일반적 프로그래밍'이라고 이야기 합니다. 저도 처음에는 그렇게 들었습니다. 

그러나 이것은 잘못된 표현이지 않을까 생각합니다. 영어 사전을 보면 Generic 이라는 것은 '총칭(總稱)적인' 이라는 뜻도 있는데 이것이 '일반적'이라는 단어보다 더 확실하며 제가 2004년에 일본에서 구입한 "C++ 설계와 진화(Bjarne Stroustrup 저)"라는 일본 번역서에도 Generic은 총칭으로 표기하고 있습니다. 

그럼 Generic Programming은 무엇일까요? 

네이버 사전에서 Generic 이라는 단어를 검색하면, 

3【문법】 총칭적인
the generic singular 총칭 단수 《보기:The cow is an animal.》 

라는 부분이 있습니다. 보기의 영문을 저의 짧은 영어 실력으로 번역을 하면 '암소는 동물이다' 입니다. 소는 분명히 고양이나 개와는 다른 동물이지만 '동물'이라는 것으로 총칭할 수 있습니다. 

대체 C++언어에서 무엇을 '총칭'화 할까요? 

제가 만드는 프로그램은 Windows 플랫폼에서 실행되는 '온라인 게임 서버' 프로그램입니다. 온라인 게임 서버를 만들 때는 어떤 기능이 있어야 되는가를 정한 후 클래스를 만듭니다. 클래스는 아시는 바와 같이 멤버 변수와 멤버 함수로 이루어져 있습니다. 그리고 멤버 함수도 그 내용은 저의 생각에 의해 변수들이 조작으로 되어 있습니다. 

'암소는 동물이다'라는 식으로 C++ 언어에서 총칭을 하는 것은 변수의 타입(type)을 총칭화 하는 것입니다.

  • 템플릿을 이용하면 총칭화된 타입을 사용하는 클래스와 함수를 만들 수 있습니다.
  • 템플릿을 사용하면 타입에 제약을 받지 않는 로직을 기술 할 수 있습니다.
  • Generic Programming을 하기 위해서는 템플릿이 꼭 필요합니다.

STL이 무엇으로 만들어졌나요? 네 템플릿으로 만들어졌습니다. STL은 Generic Programming으로 만들어진 가장 대표적인 예입니다. 

긴 설명은 그만하고 코드를 볼까요? 

제 나름대로 템플릿을 이해하는 데 도움이 되었으면 해서 이런저런 이야기를 했는데 과연 도움이 되었는지 모르겠네요. 아마 설명만 듣고서는 템플릿에 대해 명확하게 이해를 하지 못하리라 생각합니다. 우리 프로그래머들은 정확하게 이해하려면 코드를 봐야겠죠? 템플릿은 크게 함수 템플릿과 클래스 템플릿으로 나눌 수 있습니다. 

다음 시간에는 함수 템플릿에 대해서 이야기하겠습니다.


출처 : http://www.hanb.co.kr/

Trackback 0 And Comment 0

숫자에 세자리마다 콤마를 찍는 알고리즘

|
int
util_add_comma_to_num(const char *str, char *buf, int buflen)
{
    int len;
    int shift;

    /* count given string */
    len = strlen(str);
    shift = -len;

    assert( buflen >= (len + len/3 +1));

    while (*str)
    {
        *buf++ = *str++;
        if (++shift && (shift % 3) == 0)
            *buf++= ',';
    }

    *buf = '\0';

    return 0;
}

출처 : http://kldp.org/node/38269

Trackback 0 And Comment 0

Duff's Device

|

출처 : http://allwiz.egloos.com/viewer/2803607


루프 최적화란 루프의 반복 회수를 줄이는 것을 말할 것입니다. 왜냐하면, 루프와 같은 분기 명령(JMP)이 많이 발생하면 할 수록 시스템의 성능을 저하될 것이기 때문입니다. 제 경우 현재 개발중인 프로그램에서 검색, 삽입, 수정, 삭제 처리를 위해 루프가 많은 구조다 보니, 이 루프들을 어떻게 효율적으로 줄일 수 있을까? 고민하다가 Duff's Device를 발견하게되었습니다.

1. Duff's Device
Duff's Device는 1983년 루카스필름에서 일하던 Tom Duff가 애니메이션 재생시 생기는 병목현상을 해결하기 위해 만들었다고 합니다. 그 당시에도 많은 논란이 있었다고 합니다만, 이 코드를 처음 본 제 느낌은 '우앗!' 이었습니다. 그리고 가까운 시일 내에 좀 더 테스트를 해보겠지만, 적용된다면 일반 루프에 비해 훨씬 효율적일것이라 생각합니다.

1.1 일반적인 복사
아래의 함수는 nArrSrc int형 배열의 값을 nArrDest로 복사하는 함수입니다.


const int MAX_ARRAY_SIZE = 10;

void CopyArray(int* nArrSrc, int* nArrDest, int nCount)
{
    do
    {
        *nArrDest++ = *nArrSrc++;
    } while( --nCount > 0 );
}

...
int nArrSrc[MAX_ARRAY_SIZE];
int nArrDest[MAX_ARRAY_SIZE];
...
CopyArray(nArrSrc, nArrDest, MAX_ARRAY_SIZE);


위의 예는 정상적으로 작동합니다. 그런데 만약 nCount가 1000번 혹은 10000 번이라면 얘기는 조금 달라지게 될 것입니다. 즉, 앞서 말한바와 같이 루프는 성능을 저하시키는 한 요소이기 때문입니다

1.2 Duff's Device 적용
앞서 구현한 CopyArray 함수는 Duff's Device에 의해 다음과 같이 수정될 수 있습니다.


const int REPEAT_COUNT = 8;

void CopyArrayEx(int* nArrSrc, int* nArrDest, int nCount)
{
    switch( nCount % REPEAT_COUNT )
    {
    case 0 :
        do {
        *nArrDest++ = *nArrSrc++;
    case 7 :
        *nArrDest++ = *nArrSrc++;
    case 6 :
        *nArrDest++ = *nArrSrc++;
    case 5 :
        *nArrDest++ = *nArrSrc++;
    case 4 :
        *nArrDest++ = *nArrSrc++;
    case 3 :
        *nArrDest++ = *nArrSrc++;
    case 2 :
        *nArrDest++ = *nArrSrc++;
    case 1 :
        *nArrDest++ = *nArrSrc++;
        } while ( (nCount -= REPEAT_COUNT) > 0 );
    }
}


위의 코드를 보면, switch-case문을 마치 goto문 처럼 사용한 것을 보실 수 있는데, 이를 보고 개인적으로 놀랐습니다. 세상엔 참 머리 좋은 사람이 많구나 하고요. 또한 REPEAT_COUNT로 8을 사용하였는데, 이는 캐쉬 크기에 맞춘 작업으로 보입니다. 일반적으로 위와 같이 loop unrolling 기법을 사용할 때에는 루프를 무한정 늘리는 것이 아닌 캐쉬 크기에 맞춰 8 혹은 16배의 loop unrolling을 사용한다고 합니다.

※ 참고 자료
• http://www.lysator.liu.se/c/duffs-device.html
• http://en.wikipedia.org/wiki/Duff's_device 
• 여인춘, 프로그래밍 고수의 알고리즘 , 마이크로 소프트웨어 2006년 10월호, pp.298-pp.300, Loop unrolling
 

'잡다한것들전부 > C, C++, C#' 카테고리의 다른 글

동기화 비동기화 동기식 비동기식 이란?  (0) 2014.01.13
추상 클래스 (C++)  (0) 2014.01.10
Duff's Device  (0) 2014.01.10
c++ 11 이란??  (0) 2014.01.09
strncpy 로 메모리 복사  (0) 2014.01.07
C언어 배열 초기화 방법  (0) 2014.01.07
Trackback 0 And Comment 0

[iTween] 유도 미사일 구현 관련 내용

|

출처 :https://sites.google.com/site/changgugaebaljeongli/unity3d/geim-gineung


유도 미사일 구현 - iTween

게시자: Changgu Jeong, 2013. 4. 2. 오후 11:10

using UnityEngine;

using System.Collections;


public class Missile : MonoBehaviour {

public float missileSpeed = 5.0f;

public float missileDelay = 0f;

void Update () {

  Vector3 testVec =Camera.main.ScreenToWorldPoint(Input.mousePosition);  - 스크린 좌표를 게임내 좌표로 변경 시켜주는 부분

  testVec.z = 0f;   - 2D용으로 만드는 경우 z값은 받아오지 않는다

  iTween.LookUpdate(gameObject,iTween.Hash("looktarget",testVec,"time",20));   - 미사일이 마우스쪽을 바라 보도록 회전 시켜주는 부분

  iTween.MoveUpdate(gameObject,iTween.Hash ("position",testVec,"time",missileSpeed));  - 미사일이 마우스쪽을 향하도록 일정한 속도로 이동시켜 주는 부분


}

}



Trackback 0 And Comment 0

[좌표] 좌표에 대해서 알아봅시다(convertToWorldSpace) 절대좌표

|

[convertToWorldSpace]


convertToWorldSpace 함수에 대해서 알아봅시다.

예를 들어 게임을 제작시 배경하면을 스크롤 하는 기능을 넣을 시 배경화면에 자식노드를 넣고 처리할때 자식노드의 좌표를 알고 싶을때가 있다.

convertToWorldSpace는 부모 노드의 좌표를 기준으로 자식노드의 좌표를 구합니다.


               CCSize size = CCDirector::sharedDirector()->getWinSize(); CCSprite * background = CCSprite::create("HelloWorld.png"); background->setPosition(ccp(0,0)); this->addChild(background); CCSprite *closeSprite = CCSprite::create("CloseNormal.png"); closeSprite->setPosition(ccp(10,10)); background->addChild(closeSprite); CCPoint convertPos = background->convertToWorldSpace(closeSprite->getPosition()); CCLog("%2.f %2.f",convertPos.x, convertPos.y); CCLog("%2.f %2.f",closeSprite->getPosition().x, closeSprite->getPosition().y);



출력결과

-230 -150 10 10


배경화면이 480*320인 스프라이트를 0,0 좌표에 add해줄시 화면의 중간에 위치하게 됩니다.





close 스프라이트는 배경화면 기준으로 10,10 에 위치하게 되며

배경화면은 -240,-160 에서 부터 그려지게 되므로

배경화면을 기준으로 한 절대 좌표는 -230, -150이 됩니다.


만약 배경화면의 좌표를 480,320 으로 수정하면 아래 그림처럼 보이며

로그 값은

250,170

10,10 이 됩니다.





여기서 알수 있듯이. convertToWorldSpace는 setposition으로 준 값과 관계없이

디바이스상에서 보여주는 좌표를 기준으로 좌표를 변형해줍니다.






Trackback 0 And Comment 0

[블로그] 블로그에 코딩 색 입히는 방법.

|

가장 쉬운 방벙은

visual studio 에서 작업한 내용을  ms word에 붙이고 그걸 다시 복사해서 블로그에 붙이면 소스 코드 색을 그대로 입힐수 있음


다른 방법은 사이트를 통한 방법

http://markup.su/highlighter/


여기에서 코드를 입력하고 highlight버튼을 클릭하면 변형이 됨.


Trackback 0 And Comment 0
prev | 1 | 2 | next