2017. 4. 6. 13:21

안녕하세요. 게임 개발자 놀이터 입니다.


C#에서 자주썻던 이벤트 콜백 함수를 C++에서 사용하고 싶어서 알아봤습니다.


여러번 실패 끝에 성공해서 이곳에 공유하려고 합니다~


코드먼저 보시죠!


헤더 파일입니다.


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
class Button : public CSprite
{
public:
    Button();
    ~Button();
 
private:
    typedef std::function<void()> Event; //인수가 없는것
 
    short state;
    Event OnClick;
    Event OnMouseOver;
    Event OnMouseOut;
 
public:
 
    void SetOnClick(Event Function);
    void SetOnMouseOver(Event Function);
    void SetOnMouseOut(Event Function);
 
    virtual void MouseEvent(DIMOUSESTATE MouseState) override;
 
 
 
};
cs


여기서 상속받는 CSprite와 가상함수 MouseEvent는 무시하셔도됩니다. ( 제가 만든 엔진에 일부입니다..)


다음은 소스 코드입니다.


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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Button::Button()
{
    state = 0;
    OnClick = NULL;
    OnMouseOver = NULL;
    OnMouseOut = NULL;
}
 
 
Button::~Button()
{
}
 
void Button::SetOnClick(Event Function)
{
    OnClick = std::move(Function);
}
void Button::SetOnMouseOver(Event Function)
{
    OnMouseOver = std::move(Function);
}
void Button::SetOnMouseOut(Event Function)
{
    OnMouseOut = std::move(Function);
}
 
void Button::MouseEvent(DIMOUSESTATE MouseState)
{
    CPoint mouse = Director::GetInstance()->Get_Mouse();
 
    if (this->GetBoundingBox().containsPoint(mouse) && state != 1)
    {
        //MouseOver
        if (OnMouseOver != NULL)
            OnMouseOver();
        state = 1;
    }
    else if (!this->GetBoundingBox().containsPoint(mouse) && state != 0)
    {
        //MouseOut
        if (OnMouseOut != NULL)
            OnMouseOut();
        state = 0;
    }
    else if (this->GetBoundingBox().containsPoint(mouse) && state == 1)
    {
        //MouseClick
        if (MouseState.rgbButtons[0& 0x80)
        {
            if (OnClick != NULL)
                OnClick();
            state = 2;
        }
    }
}
cs



음.. 소스는 딱히 볼게 없군요!


std::move로 넣어줘야 한다는것 정도!?


그럼 적용법을 알아볼까요.


1
2
3
4
5
6
7
8
9
10
11
12
void CTestScene::TestFunction()
{
    CCLog("버튼 테스트 함수 호출!");
}
 
void CTestScene::ButtonInit()
{
    ButtonTest = new Button();
    ButtonTest->SetOnClick(std::bind(&CTitleScene::TestFunction, this));
 
    m_default_Layer->AddChild(ButtonTest);
}
cs


여기서 중요한 부분은 9번째줄이네요


std::bind로 넘겨주는것입니다.



음.. 뭐랄까.. 무언가 만들었다기보단 있는 기능을 사용한거라 글을 적기 애매하네요.


질문은 댓글로 부탁드려요

Posted by 시리시안

댓글을 달아 주세요

2016. 12. 5. 13:15

안녕하세요. 게임개발자 놀이터 입니다.


이번 포스팅에선 C++의 예약어중 하나인 inline에 대해 포스팅 하려고합니다.


C++ 책을 보다보면 꼭 나오는 예약어중 하나인데요.


쉽게 말하자면, 인라인 함수는 컴파일된 함수의 코드가 프로그램의 코드안에 직접 삽입이 되는겁니다.


즉, 컴파일러가 함수를 호출하는 과정이 사라져 버리는것이죠.


코드를 볼까요?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>
 
using namespace std;
 
inline void Inlinefuction()
{
    cout<<"*** Inlinefuction() ***"<<endl;
}
 
 
int main()
{
 
    Inlinefuction();

    return 0;
}
cs


이렇게 선언한 코드를 컴파일 하게되면


1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
 
using namespace std;
 
int main()
{
 
    cout<<"*** Inlinefuction() ***"<<endl;
 
    return 0;
}
cs


위와 같이 바뀌게 됩니다. 어떤가요?


좀더 확실한 결과를 보기 위해 어셈블리 코드로 확인 해보겠습니다.


Visual C++에서 어셈블리 파일을 생성할떄 /Ob1 이라는 추가 명렁 옵션을 주셔야 어셈블리 코드가 정상적으로 출력됩니다.

(진짜 몰라서 엄청 고생했네요...)


먼저 인라인이 적용 되지 않은 어셈블 코드중 메인 함수를 보면


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
; 12   : {
 
    push    ebp
    mov    ebp, esp
 
; 13   :     Inlinefuction();
 
    call    ?Inlinefuction@@YAXXZ            ; Inlinefuction
 
; 14   :     return 0;
 
    xor    eax, eax
 
; 15   : }
 
    cmp    ebp, esp
    call    __RTC_CheckEsp
    pop    ebp
    ret    0
cs


위와 같이 나오게 됩니다. 그후 Inline이 적용된 함수의 어셈블 코드를 보면


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
31
32
; 12   : {
 
    push    ebp
    mov    ebp, esp
    push    esi
 
; 13   :     Inlinefuction();
 
    mov    esi, esp
    mov    eax, DWORD PTR __imp_?endl@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@Z
    push    eax
    push    OFFSET ??_C@_0BI@FMEKNHNP@?$CK?$CK?$CK?5Inlinefuction?$CI?$CJ?5?$CK?$CK?$CK?$AA@
    mov    ecx, DWORD PTR __imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A
    push    ecx
    call    ??$?6U?$char_traits@D@std@@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@PBD@Z ; std::operator<<<std::char_traits<char> >
    add    esp, 8
    mov    ecx, eax
    call    DWORD PTR __imp_??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z
    cmp    esi, esp
    call    __RTC_CheckEsp
 
; 14   :     return 0;
 
    xor    eax, eax
 
; 15   : }
 
    pop    esi
    cmp    ebp, esp
    call    __RTC_CheckEsp
    pop    ebp
    ret    0
cs


이렇게 나오게 됩니다.


13번째 줄을 보면 확연히 차이가 나는걸 보실수 있습니다.


음.. 보기 어렵군요?


함수를 좀더 보기 쉽게 바꿔보겠습니다.


소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
 
using namespace std;
 
int Inlinefuction(int a, int b)
{
    return a+b;
}
 
 
int main()
{
    int n;
    n = Inlinefuction(1,2);
    return 0;
}
cs


Inline이 적용하지 않은 어셈블리 코드

1
2
3
4
5
6
7
8
9
10
11
12
; 13   :     int n;
; 14   :     n = Inlinefuction(1,2);
 
    push    2
    push    1
    call    ?Inlinefuction@@YAHHH@Z            ; Inlinefuction
    add    esp, 8
    mov    DWORD PTR _n$[ebp], eax
 
; 15   :     return 0;
 
    xor    eax, eax
cs


Inline이 적용된 어셈블리 코드

1
2
3
4
5
6
7
8
9
10
; 13   :     int n;
; 14   :     n = Inlinefuction(1,2);
 
    mov    eax, 1
    add    eax, 2
    mov    DWORD PTR _n$[ebp], eax
 
; 15   :     return 0;
 
    xor    eax, eax
cs



14 번의 코드를 보면 확 차이가 느껴지실껍니다.

가장 크게 어셈블리 코드상에서 call이 불리지 않게됩니다.

또 지금 코드상에선 push도 호출되고 있지 않네요.



일반 함수는 호출되는 과정을 보면, 우선 매개변수를 스택이 집어넣고, 함수를 호출합니다. 그후 함수내 리턴값을 위한 스택의 push, pop 도있고, 리턴후 스택 정리를 위한 pop이 있습니다.

이러한 함수 호출은 스택에 접급 하기 때문에 즉, 메모리에 접근 하기 때문에 속도가 저하 될 수 있습니다.

함수 호출에 대한 자세한 이야기는 다른 포스팅에서 다루겠습니다.



인라인을 쓰게되면 가장 먼저 속도와 메모리 면에서 효과가있습니다.

메모리는 위에 적은 것처럼 함수를 부르기 위해 스택에 넣었다 빼는 과정이 없어지기 때문이고, 그에따라 속도 면에서 더 빨라진답니다.


하지만 단점은 너무 반복해서 사용하게 될경우 어셈블리 코드가 길어진다는 것입니다. push와 call로 끝나는 함수가 여러번 호출되면서 여러번의 내용이 다 복사되서 어셈블리 코드로 적히게 되면 그 길이는 장난 아니겠죠?



결론


음.. 제 짧은 지식으로 내린 결론은 

함수 구문이 짧고, 호출이 적은 함수는 inline화 시키자는 겁니다. 크게 영향을 주지않는 함수 호출을 줄이자는거죠..!!


잘 모르는 분야라 댓글로 지적 해주시면 감사하겠습니다.






Posted by 시리시안

댓글을 달아 주세요

2016. 11. 30. 13:51

안녕하세요. 게임개발자 놀이터입니다.


이번 포스팅에선 C++로 Swap 함수를 짜보려고합니다.


흔히 Swap 함수를 짠다고 하면 자료형 int에 한애 대부분 이렇게 짤것입니다.


1
2
3
4
5
6
void Swap(int *a, int *b)
{
    int temp = *b;
    *= *a;
    *= temp;
}
cs


int형에 한에 정말 간단하고 편한 방법이지요.


main에서는 다음과 같이 사용할것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
 
    int a =10;
    int b =50;
 
    cout<<"*** Swap 함수 실행 전 2개의 값 : "<<a<<" , "<<b<<" ***"<<endl;
    Swap(&a,&b);
    cout<<"*** Swap 함수 실행 후 2개의 값 : "<<a<<" , "<<b<<" ***"<<endl;
 
    return 0;
}
cs


실행 결과는 다음과 같겠죠??


하지만 여기서 Swap을 숨겨저 포인터 인자인지를 모른다면..??


한번쯤은 이렇게 쓰고 싶어질것입니다.


1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
 
    int a =10;
    int b =50;
 
    cout<<"*** Swap 함수 실행 전 2개의 값 : "<<a<<" , "<<b<<" ***"<<endl;
    Swap(a,b);
    cout<<"*** Swap 함수 실행 후 2개의 값 : "<<a<<" , "<<b<<" ***"<<endl;
 
    return 0;
}
cs


그럼 Swap 함수는 이렇게 고쳐야 할꺼애요.


1
2
3
4
5
6
void Swap(int &a, int &b)
{
    int temp = b;
    b = a;
    a = temp;
}
cs


어느게 더 좋아 보이나요??


뭐.. 사용자에 다를테니.. 자세한 의견댓글로 남겨주시면 감사하겠습니다..


하지만 위의 방식은 int자료형에 한정되어있다는점이 아쉽죠.


char는? float는?


그럼 그때마다 함수를 계속 새로 만들어야 할까요?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void Swap(int &a, int &b)
{
    int temp = b;
    b = a;
    a = temp;
}
 
void Swap(float &a, float &b)
{
    float temp = b;
    b = a;
    a = temp;
}
 
void Swap(char &a, char &b)
{
    char temp = b;
    b = a;
    a = temp;
}

cs


이렇게 말이죠..? 너무 비효율적이죠. Swap이란 이름을 가진 함수는 뭐든간에 2개의 자리 또는 서로 교체 한다는 뜻일텐데 말이죠.


그래서 Template를 이용합니다.


1
2
3
4
5
6
7
template<typename T>
void Swap(T &a, T &b)
{
    T temp = b;
    b = a;
    a = temp;
}

cs


이러면 어떤가요? 이렇게 하면 어떤 자료형이라도 Swap이 가능합니다.


밑은 풀소스입니다.


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
#include<iostream>
 
using namespace std;
 
template<typename T>
void Swap(T &a, T &b)
{
    T temp = b;
    b = a;
    a = temp;
}
 
 
int main()
{
 
    int a =10;
    int b =50;
 
    cout<<"*** Swap 함수 실행 전 2개의 값 : "<<a<<" , "<<b<<" ***"<<endl;
    Swap(a,b);
    cout<<"*** Swap 함수 실행 후 2개의 값 : "<<a<<" , "<<b<<" ***"<<endl;
 
    return 0;
}
cs


감사합니다.


Posted by 시리시안

댓글을 달아 주세요

2016. 11. 30. 11:57

안녕하세요. 게임개발자 놀이터 입니다.

오늘은 C++에서 자주 쓰이지 않는! (개인적인 기준입니다..! 전 지금까지 써본적이 없었어요) friend에 대해서 정리 해볼까 합니다.


먼저 friend의 뜻을 생각해볼까요? 제목에도 적어놨지만 [친구]라는 뜻을 가지고 있습니다. C++에서 friend를 선언하면 친구 관계가 되는 거랍니다.  클래스끼리 말이죠. 그것도 굉장히 [친한 친구]! 베스트프렌드! 라고 생각하시면 이해가 좀 쉬울꺼애요.


모두가 그런건 아니겠지만, 여기선 이렇게 생각해봅시다. 

'나는 내 친한친구에게 나의 모든걸 보여줘도 괜찮아.'

'내 친구에게는 숨기는게 없어'

라는 뜻으로 friend를 생각하면 됩니다.


즉, A클래스가 B클래스를 friend로 지정한다면 B클래스는 A클래스의 private 멤버나 함수까지 접근이 가능합니다.

여기서 B클래스도 A클래스를 friend로 지정한다면 서로간의 private멤버나 함수까지 접근이 가능합니다.

(물론 B클래스는 A클래스를 friend로 지정 안할수도있습니다. (일방적인 사랑) )


선언 방법은 클래스 내부에 friend 로 선언해주면 되는데 이때 위치는 private, protected, public 어디든 상관 없습니다.


1
2
3
4
5
6
7
8
9
10
11
12
class A
{
    // 어디든 상관 없다! 
private:
    friend B;
 
protected:
    friend B;
 
public:
    friend B;
}
cs


위 코드처럼 firend는 어디에 선언되든 상관없습니다. ( 위 코드에선 A클래스가 선언되기전 B클래스를 알고있다고 생각합시다.)


그럼 직접적인 사용 예를 한번 보겠습니다.


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
31
32
33
34
35
36
37
38
#include<iostream>
 
using namespace std;
 
class B;
 
class A
{
 
private:
    int value;
public:
    A(int data)
    {
        value = data;
    }
    friend B;
};
 
class B
{
public:
    void fb(A a){
        cout<<"*** B클래스 public 함수 ***"<<endl;
        cout<<"*** A클래스 priavte int value에 접근 : "<<a.value<<" ***"<<endl;
    }
};
 
int main()
{
    A a(10);
 
    B b;
 
    b.fb(a);
 
    return 0;
}
cs


위 코드에서 25번째줄을 봅시다.

인자로 받아온 클래스 A의 value값에 접근하고있습니다.

value값은 private라 일반적으로는 절대 접근 할 수 없습니다.


하지만 friend가 이를 가능하게 만들어 줍니다.


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


즉 friend로 선언 받은 이상 A클래스의 모든걸 접근할 수 있다는 것입니다.


이처럼 friend는 언뜻보면 아무런 이상 없고 정말 편해 보이지만, 이 friend는 객체지향의 핵심중 하나인 '정보 은닉'을 깨부수는 행위를 일으키게 됩니다.


어떤 클래스라도 private에 넣어둔 이유가 분명이 있을텐데, 이를 무시하고 접근해서 원한다면, 수정을 해버릴수도 있죠.


그래서 전 friend를 써본적이 없습니다..


이상으로 friend관련 글을 이만 마칩니다.


감사합니다.




Posted by 시리시안

댓글을 달아 주세요

2016. 7. 28. 10:11

const ( 상수 )


많이 들어봤으나, 사용하는 사람은 자주 사용하고, 사용하지 않는 사람들은 자주 사용하지 않을것입니다.


상수 (constant)는 값을 '절대로' 바꿀 수 없다. 라는 특징을 가지고 있습니다.

또한 정의시 무조껀 값을 지정해주어야 합니다.


const ( 자료 타입 ) ( 상수 명 ) = ( 상수 값 );


1
2
3
4
5
6
#include <iostream>
 
int main()
{
    const int c = 10
}
cs


이런식으로 말이죠.


하지만 const의 위치는 여러군데에 붙을 수 있답니다.


1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
 
int main()
{
    int a = 10;
    
    const int * c1 = &a;     
    int * const c2 = &a; 
 
 
}
cs


위와 같은 코드를 보시면, c1과 c2의 차이점을 아시겠나요?


간단하게 생각하면 어떤게 상수화가 되는지를 생각하면 됩니다.


c1의 경우 정수형 포인터가 됩니다. 즉 c1의 메모리 주소를 변경이 가능하지만, 메모리에 있는 값을 변경하지 못하게 합니다.


하지만 c2는 상수형 포인터가 됩니다. c2의 메모리주소는 상수화 되어서 변경이 불가능하지만, 메모리에 있는 값은 변경이 가능하게 됩니다.


즉 이런 대입이 가능합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
 
int main()
{
    int a = 10;
    
    const int * c1 = &a;     
    int * const c2 = &a; 
    
    int b = 20;
    
    c1 = &b;
    c2 = 100;
 
}
cs


이 의외에 대입을 하면 에러가 나니, 직접 해보시기 바랍니다.



Complite const 와 Runtime const


C++에서 const에는 2가지의 종류가 있습니다.


바로 컴파일 상수와 런타임 상수 입니다.


어떤 차이 냐면 아래 소스를 보시면 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
 
int main()
{
    int m_1 = 10;
    int array01[m_1];  // error.
 
    const int m_2 = 10;
    int array02[m_2];  // ok..
 
    const int m_3 = s1;
    int array03[m_3];   // error.
}
cs


위 소스 에서 가운데 있는 array02를 제외하곤 전부 에러가 발생합니다.


이유가 뭘까요?


먼저 array01은 배열의 크기가 변수이기때문에 에러가납니다.


반대로 array02는 상수이기때문에 가능하지요. 하지만,


array03은 상수를 넣었는데 왜 에러가 날까요?


이는 array02에 쓰이는 m_2는 컴파일 상수이며, array03에 쓰이는 m_3는 런타임 상수이기 때문입니다.


실제로 코드를 보시면 m_3는 프로그램이 실행되어 배열 생성까지 도달할때 m_3에 대입되는 s1의 값을 모릅니다.


위 코드는 간단해서 생각만으로도 값을 알아낼 수 있지만, 좀더 복잡한 코드가 된다면 눈으로도 알아낼 수 없을것입니다.


이런 컴파일 상수와 런타임 상수과 확실하게 나눠진다면 정말 편할텐데 말이죠.


(PS. C++11에서는 이를 해결 할 수 있는 constexpr 이 있습니다. 참고하세요.)



const 위치에 따른 쓰임 변화


const는 정말 여러 위치에 놓일 수 있습니다.


변수 선언 시 : [const] 자료형타입 [ const ] 포인터 [const] 변수명

함수 선언 시 : [const] 자료형타입 [ const ] 포인터 [const] 함수명(매개변수...) [const { }


함수 변수를 생각하면 총 7군데나 되네요.. 많아라..


겹치는곳을 생각하면 총 4가지입니다. 걱정마시죠!


그럼 먼저 다시 짚고 가야할 부분이 있습니다.


const(상수화)가 되면 값 변경이 불가능하다.


그럼 이제 찾아야 할 점을 '어디서' 또는 '어떤게' 값 변경이 안되는가 입니다.



그럼 몸풀기로 생각을 해봅시다.


const int * * a;

int *const * a;

int * * const a;


이렇게 3개의 변수가 각각 다른 위치에 const를 달고 있습니다. 이 3개를 구분지어 볼까요?


먼저 '어디서', '어떤게' 영향을 받았나가 중요합니다.


그럼 제일 크게 const를 기반으로 자료형과 변수명을 나눌수있습니다.


(const int) * * a;

(int *const) * a;

(int **) const a;


위 처럼 나눠서 보면 더 쉬울까요?


첫번째 (const int) * * a; 은 상수화된 int의 이중포인터 라는 것입니다.

즉 해석하면 가능과같습니다.

const int x =10;

const int * y = &x;

이때 &x 가 const int ** 라고 형입니다.

즉, (const int) * * a; 에선 알맹이 값인 a가 상수처리 되는것이죠.


다시 말하면

a (값변경가능 = 주솟값)

*a (값 변경가능 = 주솟값)

**a (값 병경 불가능 , 상수)

입니다.


2번쨰 (int *const) * a; 은 int*이 가지고 있는 주솟값 즉 *를 상수화 시킨겁니다.


가리키는 방향을 상수화 한거죠.


간단하죠?


3번쨰는 (int **) const a;

int **형 자체가 상수화가 된겁니다.


즉 처음으로 가리키는 주솟값이 상수화가 된것이지요.


함수도 비슷합니다. 하지만 마지막으로 함수 맨뒤에 붙는 const는 함수 내부 내용을 상수화 하겠다는 뜻입니다. 즉, 함수 내부에서는 값을 변화시키는 행위를 일절 하지 못하게 막는다는 소리입니다.


감사합니다



Posted by 시리시안

댓글을 달아 주세요

2016. 7. 27. 12:49



C++ 캐스팅(cast)에는 총 4가지 종류가 있습니다.


바로


static_cast

reinterpret_cast

const_cast

dynamic_cast 

입니다.


각각 무슨 기능을 하는지 알아 보자면


 

 static_cast


명시적 형 변환을 위한 캐스트 연산자


 reinterpret_cast


무조건 적으로 변환한다.(강제 변환)  ( reinterpret 다시 해석하다, 새로 해석하다.) 메모리 단위로 분해하여 재조립합니다. 굉장히 위험한 방법입니다.


 const_cast

 

const 즉 상수성을 제거 할때 사용됩니다.


 dynamic_cast 

 

RTTI( Run Time Type Information )를 위한 캐스팅 입니다.



하지만, 굳이 귀찮게 위의 방법을 써야하는 이유가 뭘까요?


저희가 C언어 쓸때는 그냥 (Type) 으로 캐스팅 연산을 했었습니다.


다음과 같은 경우를 봅시다.


1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
 
int main()
{
 
    int m_4byte = 0;
    double* m_pointer = (double*)&n;
    *m_pointer = 1.23;
 
    return -1;
}
cs


무엇이 문제 인지 보이시나요?


위와 같은 방법을 사용하면 어떻게 될까요?


에러가 납니다.  직접 VS를 열어서 빌드해보세요.


그럼 다음 경우를 보겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
 
int main()
{
 
    
    const int c_int = 50;
    int * p_int = (int*)&c_int;
    *p_int = 100;
 
    cout << c_int << endl;
 
    cout << *p_int << endl;
 
    return -1;
}
cs

다음과 같이 코드를 작성 한 후 실행하면 어떤 결과가 나올까요?
---------------------------
50
100
---------------------------
이 나오게 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
 
int main()
{
 
    
    int value = 50;
    const int c_int = value;
    int * p_int = (int*)&c_int;
    *p_int = 100;
 
    cout << c_int << endl;
 
    cout << *p_int << endl;
 
    return -1;
}
cs


하지만 다음과 같이 코드를 작성 한 후 실행 하게되면

-------------------------
100
100
-------------------------


이 나오게 됩니다.


이는 const의 값이 결정되는 시점에 따라 달라집니다만, 그건 나중에 const에 관련 글을 적을때 적도록 하겠습니다.


이 2개의 코드로 알아야 하는것은 C에서 지원하는 cast 방식은, 불안전 하다는 것입니다.


2개의 코드 다 에러를 나타내지 않습니다. 하지만 비정상적인 결과를 출력하죠.


과연 이게 올바른 걸까요?


그럼 static_cast로 변경을 해봅시다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
 
int main()
{
    int value = 50;
    const int c_int = value;
    int * p_int = static_cast<int*>(&c_int);
    *p_int = 100;
 
    cout << c_int << endl;
 
    cout << *p_int << endl;
 
    return -1;
}

cs


컴파일러는 const 또는 다른 형식 한정자로 캐스팅 할수없습니다. 라며 에러를 띄우고 빌드를 하지 않습니다.

불안전 하기 때문이죠.


만약 const값을 변하게 하려는 의도가 있다면, const_cast를 사용해야 할것입니다.


그렇기에 안정하고 정상적인 캐스팅을 위하면 C++에서는 기본적으로 명시적캐스팅인 static_cast를 사용하도록 합시다.


다음은 reinterpret_cast 입니다.


reinterpret_cast는 제가 위에 이렇게 적어 놨을겁니다.


무조건 적으로 변환한다.(강제 변환)  ( reinterpret 다시 해석하다, 새로 해석하다.) 메모리 단위로 분해하여 재조립합니다. 굉장히 위험한 방법입니다.


여기서 중요하게 보셔야 할 부분은 '메모리' 단위 입니다.


메모리 단위란 무엇일까요?


그럼 이 코드를 자세히 봅시다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
 
class A {public : int a;};
class B {public : int b;};
 
class C : public A, public B
{
    int c;
};
 
int main()
{
    C c;
    return -1;
}
cs


위의 코드에서 class C는 class A와 B를 다중 상속 받은 상태입니다. 그리고 A와 B는 각각 4byte짜리의 int를 가지고 있죠.

그럼 C를 선언 하는 순간 메모리는 어떻게 잡힐까요?


Class C

 Class A

 int a;

 4 Byte

 Class B

 int b;

 4 Byte

 C클래스 멤버

 int c;

 4 Byte


다음과 같이 구조를 가질껍니다.


그럼 위와 같은 메모리 구조를 가졌을때 아래와 같이 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
 
class A {public : int a;};
class B {public : int b;};
 
class C : public A, public B
{
    int c;
};
 
int main()
{
    C c;
    A* p_a = &c;
 
    cout << &<< end;    
    cout << p_a << end;
 
    return -1;
}
cs


코드를 작성 한후 실행하면 2개의 결과 값은 똑같이 나올겁니다.


하지만 이 코드를 어떨까요?



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
 
class A {public : int a;};
class B {public : int b;};
 
class C : public A, public B
{
    int c;
};
 
int main()
{
    C c;
    B* p_b = &c;
 
    cout << &<< end;    
    cout << p_b << end;
 
    return -1;
}
cs


C클래스가 부모인 B클래스로 암시적 캐스팅이 된 형태 입니다.


실행 하면 결과는


------------------------------------

&c의 주소값

&c의 주소값 + 4byte 

------------------------------------


암시적 변환으로 메모리의 주소값이 바뀌었습니다.


물론 자식 -> 부모 변환이라 static_cast를 사용하셔도 됩니다.


하지만 reinterpret_cast를 사용하면 어떻게 될까요?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
 
class A {public : int a;};
class B {public : int b;};
 
class C : public A, public B
{
    int c;
};
 
int main()
{
    C c;
    B* p_b = reinterpret_cast<B*>(&c);
 
    cout << &<< end;    
    cout << p_b << end;
 
    return -1;
}
cs

놀랍게도 2개의 출력값이 같은 주솟값을 가지게 됩니다.


메모리 단위로 분해해서 재 생성 하기 때문이죠.


이와 같은 방법을 전 아직 어디서 잘 사용해야 할지 모르겠습니다. ( 나중에 알게되면 꼭 수정하겠습니다. ) 


하지만 저기서 p_b가 가진 멤버변수인 b를 수정하게 되면 A클래스가 가진 a가 수정이 된답니다.




Class C

 Class A

 int a;

 4 Byte

 Class B

 int b;

 4 Byte

 C클래스 멤버

 int c;

 4 Byte


위와 같은 구조가 

Class C

 Class B

 int b;

 4 Byte

 Class C

 int c;

 4 Byte


 ( )

 (4 Byte )


이렇게 조립 되었을뿐 메모리 주소를 변하지 않았기 때문이죠.


전 처음 이 사실을 알았을때는 정말 신기했습니다.  잘만 사용하면 메모리 단위로 잘 쪼개서 사용 할 수 있기 때문이죠.



마지막으로 dynamic_cast입니다.


dynamic_cast는 RTTI(Runtime Type Information)를 위한 캐스팅이라고 제가 위에 적었는데요.


런타임에 상속 계츨 관계를 가로지르거나 다운 캐스팅시 사용되는 캐스팅 연산자 라고 생각하시면 편합니다.


즉, 부모가 자식으로 캐스팅이 될 수 없는데, 런타임중 판단으로 가능하게 된다는거죠.


단 dynamic_cast는 다형성을 띄지 않은 객체간의 변환은 불가능하며, 컴파일이 에러를 낸답니다.


즉 다형성을 가지려면 클래스간 virtual 멤버 함수가 있어야 합니다.


코드로 표현하면 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;
 
class A {
public
    int a; 
 
    virtual void test(){}
};
 
class C : public A
{
    int c;
    virtual void test(){}
};
int main()
{
    C c;
    A* pA = &c; 
    C* c2 = dynamic_cast<C*>(pA);
    
    return -1;
}
cs


하지만 저 dynamic_cast가 올바르지 않았을경우는 null값을 반환하니 주의 하시기 바랍니다.


읽어주셔서 감사합니다

Posted by 시리시안

댓글을 달아 주세요

2016. 2. 23. 20:35


try, throw, catch 를 이용한 예외 처리 방법



예외 ( Exception ) 란? : 프로그램의 정상적인 실행을 방해는 조건이나 상태를 말합니다. ( 에러가 아닙니다.)


1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
 
 
void main()
{
    int a = 0;    
    int b =20;
    
    int c = b/a;
 
}
cs

위 상황을 봅시다. a의 값이 0이 할당된 상태에서 b를 a로 나누려고 합니다. 아직까진 오류가 아니지만, ( 물론 컴파일러에 따라서 오류로 판단할수 있습니다.) 

하지만, 오류가 아니라 판단되면, 다음식을 쓰레기값을 뱉을수 밖에없습니다. 그럴때 try catch문을 사용합니다.

try
1. 예외가 발생할만한 코드 블록을 지정합니다.
2. try{} 괄호 안에 예외 처리 대상 코드를 작성합니다. 
3. 이블록안에서 예외가 발생했을 때 throw 명령으로 예외를 던집니다.

throw
1. 프로그램이 정상적으로 실행될 수 없는 상활일 때 이 명령으로 예외를 던집니다.
2. thorw다음에 던지고자하는 예외객체를 넘김. 예외 던지면 catch문으로 이동(단: 예외 객체 타입이 맞을때만 적용됨 ), (오버로딩 가능)

catch
1. if-else문처럼 try-catch문으로 한쌍으로 쓰입니다.
2. try안에서 throw한 예외 객체에 대한 예외처리를 합니다
3. catch블록을 예외 핸들러라고 부릅니다..

사용법
try{
    if(예외조건)
       throw 예외객체;
} catch ( 타입 예외객체 ){
      예외처리;
}

사용 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>
 
 
void main()
{
    int a = 0;    
    int b =20;
    
    try{
    if(a == 0)
        throw a;
    int c = b/a;
    }
    catch(int ex){
        std::cout<<"예외 처리 발생, a가 0입니다."<<std::endl;
    }
}
cs

그냥 if문으로 바로 처리하면 되는데, 왜 굳이 catch까지 쓰나요?

라고 물어볼수 있는데, 이는 Stack Unwinding(스택 풀기) 를 이해하면 쉽다.

다음 예제를 보자
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
#include<iostream>
 
 
int AddValue_abs (int a, int b)
{
    if(a<0)
        throw a;
    if(b<0)
        throw b;
 
 
    return a+b;
}
void main()
{
    int a,b;
    
    std::cin>>a>>b;
    
    try{
        //바로 보이는 이곳엔 throw가 없다.
        std::cout<<AddValue_abs(a,b)<<std::endl;
    }
    catch(int ex){
        //예외처리가 생기면 실행
        std::cout<<"예외 처리 발생"<<std::endl;
    }
}
cs


AddValue_abs라는 함수에서 try도 없는데, 그렇다고 catch도 없는데 뜬금없이 조건 검사후 throw를 던집니다.
여기서 Stack Winding(스택풀기)를 볼수있는데, throw가 던져진 시점에서 바로 뒤에 코드는 읽지 않습니다.
그리고 뒤로 스택을 풀어 나가며, throw로 던져진 변수 타입과 같은 인자를 지닌, catch를 찾습니다.

만약 예외 처리를 if문으로 검사하게되면, 여러 조건에 따라 함수 자체의 반환값에 예외를 하거나, 예외후 처음부터 다시해야하는 번거로움이 있는데, 이걸 사용할 경우 간편하게 할수가 있습니다.

또한 Throw로 클래스 자체를 넘겨버리면, Catch에서 calss를 인자로 받아올수 있다는점..!








 




'프로그래밍 > Language' 카테고리의 다른 글

파일 입출력에 대하여  (0) 2016.02.25
구조체에 대하여  (0) 2016.02.25
try, throw, catch 를 이용한 예외 처리 방법  (0) 2016.02.23
포인터에 대하여  (0) 2016.02.23
C언어 연산자에 대하여 - 비트연산  (0) 2016.02.23
C언어 연산자에 대하여  (0) 2016.02.23
Posted by 시리시안

댓글을 달아 주세요