'공부합시다/C++'에 해당되는 글 8건

  1. 2009.08.05 연산자 오버로딩
  2. 2009.07.22 C++에서의 동적할당 1
  3. 2009.07.21 임시 객체
  4. 2009.07.21 [펌] 함수 포인터 및 클래스 멤버함수의 함수포인터화
  5. 2009.07.21 함수 const
  6. 2009.07.21 인라인(inline) 함수
  7. 2009.07.21 클래스 내 static 함수
  8. 2009.07.21 클래스내 static 변수 초기화 방법
2009. 8. 5. 11:49

연산자 오버로딩

int a;

a = 1 + 2;

위의 소스를 모르는 사람은 없을 것이다.

하지만 다음과 같은 소스가 가능할까?

MyClass obj1;
MyClass obj2;

obj1 = obj1 + obj2;

객체를 객체끼리 더해서 대입한다...라는 뜻인거 같은데 가능해보이지는 않는다.


물론 가능하지 않다.

하지만 C++에서는 가능하도록 만들 수 있다.
바로 연산자 오버로딩을 이용해서 연산자를 재정의하면 위의 코드가 가능해지도록 만들 수 있다.


더하기 연산자를 다시 한번 자세히 살펴보도록 하자.

a = 1 + 2;
라는 코드가 있다고 한다면, 이 코드를 자세히 보면 연산자 우선 순위에 의해서 +가 먼저 실행되고, =가 그 뒤에 실행되는데, + 를 보면 + 를 기준으로 앞 뒤의 정수들 더해서 정수를 만들어 준다는 것을 알 수 있다.

이를 바꿔 말하면, 두 개의 정수형 인자를 받아서 한개의 정수를 리턴해준다고도 표현할 수 있을 것이다. 마치 함수와 같이 말이다.

이것을 함수로 나타낸다면 int 더하기(int, int) 이런식으로 표현할 수 있을 것이다.

C++에서는 함수를 오버로딩(재정의)할 수 있는데 마찬가지로 연산자 또한 재정의를 할 수 있다. 따라서 이 + 를 재정의해서 객체와 객체를 어떤 조건으로 더하고 다시 객체를 리턴해주는 식으로 만들어 준다면 객체끼리의 덧셈도 가능하게 만들 수 있을 것이다.

다만, 연산자를 재정의할 때는 함수와 구별하기 위해 operator라는 예약어를 사용한다.

사용법은 다음과 같다.

리턴값 operator연산자(인자)
{
    구현
}


실제로 사용한 소스는 아래와 같다.

#include <iostream.h>

class MyClass
{
  private:
    int val;

  public:
    void SetVal(int k)
    {
      val = k;
    }
    
    void PrintVal()
    {
      cout<<val<<endl;
    }

    MyClass & operator+(MyClass &obj)
    {
      val += obj.val;
      return *this;
    }
};

int main()
{
  MyClass obj;

  obj.SetVal(5);

  MyClass test;

  test = test + obj;

  test.PrintVal();

  return 0;
}

operator+를 구현한 부분을 보면 인자를 하나로 받아서 자신의 데이터와 더하고 자신을 다시 리턴해주는 것을 볼 수 있다.

즉, test = test + obj 에서 test에 obj를 더해서 test를 다시 리턴해주는 것이다.

물론 클래스 밖에서 전역함수로 선언하여 구현을 할 수도 있지만(만약 이렇게 한다면 연산자 앞뒤의 객체 두개를 인자로 받아야할 것이다.) 이렇게 한다면, 다른 사람에 의해서 전역함수를 오버로딩하여 클래스의 private멤버들의 값을 수정하는 보안상의 취약점이 생길 수 있기 때문에 클래스의 안에서 구현하는 것이 좋다.


그런데 위의 소스에는 한가지 맹점이 있다.

만약 다음과 같은 코드가 있다고 하자.

MyClass obj;

obj = obj;

위의 코드는 보기에는 아무런 문제가 없어보인다.
하지만 만약 클래스 내에서 동적할당을 하는 부분이 있다고 한다면 심각한 문제를 발생 시킬 수 있다.

클래스의 멤버중 포인터가 동적할당을 받아 가르키고 있다면, 만약 위의 코드로 자신을 대입하게 되면 동적할당을 다시 받아 포인터가 새로 할당받은 메모리를 가르키게 될 것이고, 기존의 할당받은 메모리는 포인터를 잃어버리게 된다.

이것을 계속해서 반복하게 된다면 delete 시켜줄 수 없는 잃어버린 메모리들이 늘어나게 되고 이것은 메모리 누수로 이어지게 되어 시스템에 악영항을 끼치게 된다.

따라서 아래와 같이 고쳐주어야한다.

void operator=(MyClass &obj)
{
    if(this == &obj)
    {
        return;
    }
    //객체끼리의 대입을 구현
}

'공부합시다 > C++' 카테고리의 다른 글

C++에서의 동적할당  (1) 2009.07.22
임시 객체  (0) 2009.07.21
[펌] 함수 포인터 및 클래스 멤버함수의 함수포인터화  (0) 2009.07.21
함수 const  (0) 2009.07.21
인라인(inline) 함수  (0) 2009.07.21
2009. 7. 22. 09:48

C++에서의 동적할당

C에서의 동적할당은 malloc같은 함수를 통해 하였다. 그렇다면 C++에서도 객체 포인터를 만든다음에 malloc을 통해 동적할당이 가능할까?

다음의 소스를 보자.

#include <iostream.h>
#include <malloc.h>

class TEST
{
  public:
    char a;

    TEST()
    {
      cout<<"디폴트 생성자"<<endl;
    }
    TEST(int j)
    {
      cout<<j<<endl;
    }
    void test()
    {
      cout<<"test"<<endl;
    }
    ~TEST()
    {
      cout<<"소멸자"<<endl;
    }
};

int main()
{
  TEST *B;
  
  B = (TEST*)malloc(sizeof(TEST));
  
  B->test();
  
  free(B);
  return 0;
}

컴파일도 이상없이 되고 test()함수도 호출된다.
하지만 자세히 보면 어떤 문제가 있는데, 그것은 생성자와 소멸자가 호출되지 않는다는 것이다. 이것은 프로그래머가 의도한 동작을 수행하지 못할 가능성이 생길 수 있다.

따라서 C++에서는 다음과 같은 동적할당 키워드를 제공한다.

  TEST *A;

  A = new TEST();
  
  A->test();
  
  delete A;

new 라는 키워드를 통해 동적할당을 하고, delete 키워드를 통해 free시킨다.

다음과 같은 코드도 가능하다.

  int *a = new int;
  
  *a = 1;

  cout<<*a<<endl;

  delete a;

따라서 C++에서는 malloc 보다는 new를 통해 동적할당을 하는 것이 바람직하다.

또한 다음과 같은 방법도 가능하다.

  int *a = new int[3];
  
  a[1= 5;  // 혹은 *(a+1)

  cout<<a[1]<<endl;

  delete []a;

다만 delete를 할때는 배열이란 것을 명시해줘야 한다. 하지 않는다면 하나만 지우게 된다.

'공부합시다 > C++' 카테고리의 다른 글

연산자 오버로딩  (0) 2009.08.05
임시 객체  (0) 2009.07.21
[펌] 함수 포인터 및 클래스 멤버함수의 함수포인터화  (0) 2009.07.21
함수 const  (0) 2009.07.21
인라인(inline) 함수  (0) 2009.07.21
2009. 7. 21. 14:02

임시 객체

임시 객체는 바로 생성되었다가 사라지는 객체를 말하는데, 몇가지 용도로 쓰인다.

다음 소스를 보자.




위의 Draw 함수는 점을 찍는 함수라고 가정하자.
인자로는 points의 객체를 받고 있다.
위의 경우에서 Draw 함수에서 점을 찍을 좌표만을 받기위해 객체를 생성해서 인자로 주는데 단지 좌표만을 위해서만 생성한 객체가 메인 함수가 끝날때까지 남아있게된다.

이럴때는 임시객체를 생성하는게 더 효율적일 수 있다.

함수를 호출할 때 다음과 같이 사용하면 된다.

'공부합시다 > C++' 카테고리의 다른 글

연산자 오버로딩  (0) 2009.08.05
C++에서의 동적할당  (1) 2009.07.22
[펌] 함수 포인터 및 클래스 멤버함수의 함수포인터화  (0) 2009.07.21
함수 const  (0) 2009.07.21
인라인(inline) 함수  (0) 2009.07.21
2009. 7. 21. 12:04

[펌] 함수 포인터 및 클래스 멤버함수의 함수포인터화

< 함수 포인터 >

먼저 이 글은 포인터에 대한 이해를 필요로 한다.

포인터에 대한 기본지식이 있다고 가정하고 글을 쓰도록 하겠다.


int GetAreaEx( int x, int y )
{
    return x * y;
}


우선 이런 간단한 함수가 있다. 우리는 이 함수를 호출하기 위해 명시적으로

GetAreaEx( x, y );

이런식으로 기술해야 한다.

하지만 예를 들어 GetArea2, GetArea3, ..., GetAreaN 이런식으로 비슷한 함수가 존재하고

이를 상황에따라 다르게 호출해야 한다면 이 방식으로는 관리도 어려울 뿐더러 효율성도 떨어지고 코드량도 많이질 것이다.

또한 외부(스크립트 등)에서 어떤 특정한 함수를 호출하려 할때도 방법이 묘연할 것이다.


int (*GetArea)( int, int );

이 선언은 무엇일까?

언뜻보기에는 함수를 선언하는 것 같다.

이 선언은 함수에 대한 포인터를 선언한 것이다.

변수의 주소를 담는 포인터와 마찬가지로 함수포인터는 함수의 주소를 담는다.

GetArea = GetAreaEx; // 함수포인터 GetArea에 GetAreaEx()의 주소를 담는다
int nArea = (*GetArea)( x, y ); // (*GetArea)( x, y ); 로 GetAreaEx()함수를 호출하고 리턴받은 값을 nArea에 대입


이런식으로 GetAreaEx를 호출할 수 있다.

유의할점은 *GetArea를 꼭 ()로 감싸주어야 한다는 사실이다.

빼먹으면 컴파일러가 함수포인터를 통한 호출로 인식하지 못한다.


int (*GetArea[])( int, int ) = { GetAreaEx, GetArea2, GetArea3, ..., GetAreaN };

이것은 함수포인터 배열을 정적으로 선언한 것이다. 이렇게 배열로 기능이 비슷한 함수들을 묶어놓았다.

void CallFunc( int nState, int x, y )
{
    int nResult = (*GetArea[nState])( x, y );
}


그리고 그 함수들을 상황에 맞게 호출한다.

만약 함수포인터를 쓰지 않는다면

void CallFunc( int nState, int x, int y )
{
    int nResult;
    switch( nState )
    {
         case STATE_EX:
              nResult = GetAreaEx( x, y );
         break;
         case STATE_2:
              nResult = GetArea2( x, y );
         break;
         case STATE_3:
              nResult = GetArea3( x, y );
         break;
    }
}

위와 같이 기술해야 할 것이다.

두 방식의 차이점과 함수포인터의 이점을 알 수 있겠는가

그렇다면 함수포인터 배열을 동적으로 할당하는 방법은 없을까?

다음과 같은 방법으로 할당할 수 있다.

int (**GetArea)( int, int ); // 함수포인터의 포인터
GetArea = new (int (*[N])( int, int )); // N은 배열의 크기


그리고 다음과 같이 사용하면 된다.

GetArea[0] = GetAreaEx;
GetArea[1] = GetArea2;
GetArea[2] = GetArea3;
...

int nResult = (*GetArea[nState])( x, y );


물론 사용후 delete [] GetArea; 해서 해제하는것을 잊으면 안된다.



< 클래스 멤버함수의 함수포인터화 >

함수포인터는 함수의 주소값을 담는다고 했다.

그렇다면 클래스 멤버함수의 주소값도 단순히 함수포인터에 담아서 호출할 수 있지 않을까?

int (*func)();
func = CFunc::GetArea;


하지만 이 방법은 GetArea()멤버함수가 static으로 선언되었을 때만 가능하다.

static으로 선언되지 않은 멤버함수(멤버변수를 건들여야 하는 멤버함수)를 이 방법으로 담으려 한다면 컴파일 에러가 뜰 것이다.

여기에 다음과 같은 해결방법이 있다.

첫번째 방법은

class CFunc
{
public:
    static int GetArea( CFunc * cls, int x, int y );
};


위와 같이 선언하고 호출할때 해당 인트턴스의 포인터를 넘겨줘서

int GetArea( CFunc * cls, int x, int y )
{
    int a = cls->GetZ();
}


이런식으로 멤버변수를 읽거나 쓸수 있겠지만 이 방식으로는 한계가 있다.

Get, Set 같은 public 외부함수로 억세스하지 않으면 private나 protected안에 선언되어 있는

멤버변수는 건드릴 수 없다.

두번째는 멤버함수의 소속을 명시화하는 방법이다.

int (CFunc::*func)( int, int );
func = CFunc::GetArea;
CFunc A;
(A.*func)( x, y );


위와 같은 방법으로 해결가능하다. 물론 호출할 인스턴스가 명확해야 한다.

세번째는 클래스 안에 함수포인터를 멤버변수로 두고 별도의 함수포인터를 컨트롤하는 멤버함수를 만드는 방법이 있다.

이 방법이 멤버함수 관리가 가장 쉬우며 효율적이다.

class CFunc
{
public:
    int (CFunc::*pFunc)( int, int );
    int GetArea( int x, int y );
    void CallFunc( void ) { (this->*pFunc)( x, y ); } // CallFunc 함수호출시 자체 오버헤드를 줄이기 위해 inline
    CFunc();
    ~CFunc() {}
};

CFunc::CFunc()
{
    pFunc = GetArea;
}
int CFunc::GetArea( int x, int y )
{
    return x * y;
}

위와 같다면 CallFunc(); 로 GetArea 호출이 가능해진다.

지금은 단순히 한개의 멤버함수 호출만 할뿐 의미가 없다. 이제 실제 효율적으로 쓰이게 배열을 써보자.

class CFunc
{
public:
    int (CFunc::**pFunc)( int, int );
    int GetArea( int x, int y );
    void CallFunc( int nState, int x, int y ) { (this->*pFunc[nState])( x, y ); }
    CFunc();
    ~CFunc();
};

CFunc::CFunc()
{
    // init
    pFunc = new (int (CFunc::*[10])( int, int )); // 동적할당, 10에는 원하는 멤버함수 갯수만큼
    // 0번은 남겨둔다.

    pFunc[1] = GetArea;
    pFunc[2] = GetAreaEx;
    pFunc[3] = GetArea2;
    pFunc[4] = GetArea3;
    ...
    pFunc[9] = GetArea9;
}
CFunc::~CFunc()
{
    delete [] pFunc; // 해제
}
int CFunc::GetArea( int x, int y )
{
    return x * y;
}

자, 이제 함수하나의 호출로 상황에따라 여러 멤버함수를 호출할 수 있는 기반이 마련되었다.

CFunc A;
A.CallFunc( nState, x, y );


이렇게...

어떠한가. 함수포인터의 위력이 느껴지는가?




< STL을 이용한 함수포인터 관리 >

우리는 지금까지 함수포인터를 동적으로 배열을 할당해서 써왔다.

함수 포인터를 STL(Standard Template Library)을 써서 관리해보자.

클래스의 멤버함수의 함수포인터화에서 3번째 방법을 조금 개선시켜 보겠다.



단순히 인덱스(숫자)를 이용한 관리라면 deque정도가 괜찮을듯 싶으나,

만약 함수의 이름을 문자열로 호출하고 싶다면 map을 써볼 수 있다.

(만약 FuncCall( "GetArea", x, y ); 이런식으로 멤버함수를 호출하고 싶다면)

map은 내부적으로 트리구조를 가지고 있다.

그래서 따로 정적/동적으로 배열을 할당하지 않아도 입력된 값을 비교해서 스스로 자신의 크기를 늘린다.

mapValue["GetArea"] = 99;

이런식으로 []안에 숫자 뿐만아니라 비교할 수 있는 모든 것이 들어갈 수 있다.

먼저 map을 사용하기 위해

#include < map >
using namespace std;


를 선언한다. map은 표준 네임스페이스를 사용하므로 std의 이름공간을 활용한다.

map< []안에 들어갈 타입, 입력될 데이터타입, 비교 논리 > mapFunctor;

선언방법은 이렇게 되는데 비교 논리는 첫번째 인수가 클래스이고 안에 비교오퍼레이터가 있다면 생략가능하다

자, 이제 해보자.



struct ltstr
{
    bool operator() ( const char * s1, const char * s2 ) const
    {
         return strcmp( s1, s2 ) < 0;
    }
};

class CFunc
{
public:
    typedef int (CFunc::*_Func)( int, int );
    map< const char *, _Func, ltstr > mapFunctor;
    int GetArea( int x, int y );
    void CallFunc( const char * szFuncName, int x, int y )
    {
         (this->*mapFunctor[szFuncName])( x, y );
    }

    CFunc();
    ~CFunc();
};

CFunc::CFunc()
{
    // init
    mapFunctor["GetArea"] = GetArea;
    mapFunctor["GetAreaEx"] = GetAreaEx;
}
CFunc::~CFunc()
{
    // map 클리어
    mapFunctor.clear();
}
int CFunc::GetArea( int x, int y )
{
    return x * y;
}

char * 대신 string을 사용한다면 string안에 내부적으로 비교 오퍼레이터함수가 있기 때문에

map< string, _Func > mapFunctor;

이렇게 선언하고 사용할 수 있을 것이다.

이제 A.CallFunc( "GetAreaEx", x, y ); 란 호출로 GetAreaEx를 호출할 수 있다.

이 방식은 여러가지로 응용가능한데 스킬명에 의한 화면효과 호출이라던지

C로 미리 작성된 내부 함수를 외부 스크립트로 호출한다던지 할때 유용하게 쓰일 수 있다.

(스크립트 호출일 경우 함수이름을 인덱스화 해서 deque를 쓰는게 속도상 더 유리할 듯 하다)



< Caution >

- 귀차니즘의 관계로 클래스내에 GetAreaEx, GetArea2 등과 같은 멤버함수를 모두 기술하지는 않았습니다.
- 예제 소스는 컴파일해보지 않은 소스들이므로 오타나 잘못된 점이 있을 수도 있습니다. 지적 바랍니다.
- VS 2005 에서 에러가 납니다만, 몇가지를 수정해주시면 제대로 됩니다.
- 이 강좌는 제가 그동안 겪고 배우고 또 여기저기서 수집한 자료를 바탕으로 쓴 강좌입니다. 틀린부분이 있을 수도 있으니
그런 부분은 지적 바랍니다.
- 글의 이동은 자유지만 출처는 명시해 주시기 바랍니다.

출처: zeph's Third Story

'공부합시다 > C++' 카테고리의 다른 글

C++에서의 동적할당  (1) 2009.07.22
임시 객체  (0) 2009.07.21
함수 const  (0) 2009.07.21
인라인(inline) 함수  (0) 2009.07.21
클래스 내 static 함수  (0) 2009.07.21
2009. 7. 21. 11:35

함수 const




함수 선언 뒤에 const를 붙이게 되면 이 함수는 클래스 멤버들의 값을 변경할 수 없게 된다.
이러한 const 함수가 필요한 경우는 위와 같이 const 객체를 사용해야 할 때 이다.

const 객체는 const 함수밖에 사용할 수 없다.
2009. 7. 21. 11:12

인라인(inline) 함수

다음과 같은 매크로 함수를 보자.



\는 줄을 붙여서 만든다는 말이다. 그리고 변수마다 괄호를 친 이유는 지금의 소스에는 관계없지만 복잡해질경우 우선순위가 꼬일 수 있기 때문에 괄호를 친다.

매크로 함수는 C에서 많이 쓰였었지만 매크로 함수에는 심각한 문제점이 있는데 그것은 변수의 형을 검사하지 않는다는 것이다. 따라서 C++에서는 매크로 함수를 사용하지 않는 것을 권장하고 있다.

하지만 매크로 함수에도 장점이 있는데 일반 함수는 스택을 사용하지만 매크로는 그대로 코드가 삽입되는 것이므로 훨씬 빠를 수 있다. 따라서 이러한 매크로 함수의 장점을 대안하기 위해 C++은 인라인 함수라는 것을 제공한다.




inline 함수는 컴파일시 헤더파일에 포함되게 된다.
inline 함수는 보통 함수처럼 호출이 되면 메모리 주소로 점프하는 것이 아니라 매크로 함수처럼 소스코드가 통채로 삽입되게 된다.

'공부합시다 > C++' 카테고리의 다른 글

임시 객체  (0) 2009.07.21
[펌] 함수 포인터 및 클래스 멤버함수의 함수포인터화  (0) 2009.07.21
함수 const  (0) 2009.07.21
클래스 내 static 함수  (0) 2009.07.21
클래스내 static 변수 초기화 방법  (0) 2009.07.21
2009. 7. 21. 10:48

클래스 내 static 함수




static 함수는 객체를 생성하지 않아도 호출할 수 있다.
그렇기 때문에 static 변수가 아닌 변수들은 객체가 생성되지 않으면 없기 때문에 static 함수에서 사용할 수 없도록 해놓았다. 따라서 static 함수는 오로지 static 변수만을 사용할 수 있다.

'공부합시다 > C++' 카테고리의 다른 글

임시 객체  (0) 2009.07.21
[펌] 함수 포인터 및 클래스 멤버함수의 함수포인터화  (0) 2009.07.21
함수 const  (0) 2009.07.21
인라인(inline) 함수  (0) 2009.07.21
클래스내 static 변수 초기화 방법  (0) 2009.07.21
2009. 7. 21. 10:37

클래스내 static 변수 초기화 방법

클래스 내 static 변수의 초기 방법




static 변수는 생성자에서 초기화 할 수 없다. 왜냐하면 객체를 생성할 때 마다 static 변수가 초기화 된다면, 전역변수로서의 의미가 없어지기 때문이다. 따라서 클래스 밖에서 초기화를 해주어야 한다.

'공부합시다 > C++' 카테고리의 다른 글

임시 객체  (0) 2009.07.21
[펌] 함수 포인터 및 클래스 멤버함수의 함수포인터화  (0) 2009.07.21
함수 const  (0) 2009.07.21
인라인(inline) 함수  (0) 2009.07.21
클래스 내 static 함수  (0) 2009.07.21