반응형

EX01_읽기 엑세스 위반

 

 

 

원인 : 배열의 인덱스를 벗어난 주소를 찾아서, 금지된 영역에(kernal 영역) 엑세스 하려고 했기 때문에

 

해결 : 배열이 충분히 사이즈가 크게 만들던지, 배열을 가져오는 함수에서 제한 범위를 벗어나지 않게 해야 한다

반응형
반응형

맵의 끝(흰색별)에 도달하면 맵의 처음(검은별)이 보인다

 

 

 

 

 

맵을 렌더링 할때 발생한 것으로 보임

 

 

렌더링 코드를 살펴보면

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
void CStage::Render()
{
    /*
    맵은 스크톨 처리하여, 현재 플레이어가 이동한 위치로부터 맵을 출력해줌
    출력 크기는 세로 4칸 가로 10칸으로 해줌
    0 : 벽
    1 : 길
    2 : 시작점
    3 : 도착점
    4 : 코인
    */
    CPlayer* pPlayer = CObjectManager::GetInst()->GetPlayer();
 
    int player_Coin = pPlayer->GetCoin();
 
    //플레이어의 x,y 좌표를 얻어온다
    int player_iX = pPlayer->GetX();
    int player_iY = pPlayer->GetY();
 
    // 맵의 출력은 플레이어의 위치를 중심으로 출력
    // 세로는 플레이어 2칸위부터 한칸 아래까지 출력
    // 총 4줄이 출력되는것이다
    // 가로는 플레이어 위치부터 오른쪽 10칸 까지 출력함
    constexpr int iClientPadding_left        = 0;
    constexpr int iClientPadding_Top        = -2;
    constexpr int iClientPadding_Right        = 9;
    constexpr int iClientPadding_Bottom        = 1;
 
    int iClientHeight_start = player_iY + iClientPadding_Top;
    int iClientHeight_end = player_iY + iClientPadding_Bottom;
    int iClientWidth_start = player_iX + iClientPadding_left;
    int iClientWidth_end = player_iX + iClientPadding_Right;
 
    for (int i = iClientHeight_start; i <= iClientHeight_end; ++i)
    {
        for (int j = iClientWidth_start; j < iClientWidth_end; ++j)
        {
            if (i == player_iY  && j == player_iX)
            {
                std::cout << "§";
            }
            else if (m_cStage[i][j] == SBT_WALL)
            {
                std::cout << "■";
            }
            else if (m_cStage[i][j] == SBT_ROAD)
            {
                std::cout << "  ";
            }
            else if (m_cStage[i][j] == SBT_START)
            {
                std::cout << "☆";
            }
            else if (m_cStage[i][j] == SBT_END)
            {
                std::cout << "★";
            }
            else if (m_cStage[i][j] == SBT_COIN)
            {
                std::cout << "◎";
            }
        }
        std::cout << std::endl;
    }
    std::cout << "coin : "<< player_Coin << std::endl;
    std::cout << "Left : ← " << " Right : → " << std::endl;
    std::cout << "Jump : space bar "<< std::endl;
}
 
cs

31,32라인에 있는 iClientWidth, iClientHeight 가 곧 게임 클라이언트 화면의 가로 세로를 나타낸다

실제 맵 사이즈는 g_iMapWidth = 50, g_iMapHeight = 10 이며

iClientWidth_start , iClientWidth_end 는 각각, 맵에서 실제로 보여줄 가로 시작지점과 가로 끝점을 나타내고

iClientHeight_start, iClientHeight_end 는 각각, 맵에서 실제로 보여줄 세로 시작지점과 세로 끝점을 나타낸다

 

즉 그림으로 보면 아래와 같다

 

 

위에 버그는 iClientWidth_start 값과 iClientWidth_end 값이 일정 범위 안으로(맵의 끝에 도달하려고 했을때) 들어가면 

고정된값들이 되야 하는데, 그러지 않고 맵의 처음(0)부터 다시 출력 하게 되서 버그가 발생한듯함

즉 아래와 같은 코드가 들어가야 한다

 

     //맵 끝에 거의 도달했을때 가로 스크롤 멈춤

    //클라이언트 시작 지점이 최대 시작지점(맵 가로길이 - 클라이언트 오른쪽 여백) 보다 크거나 같을때

    //클라이언트 시작 지점 = 최대 시작지점(맵 가로길이 - 클라이언트 오른쪽 여백)

    if (iClientWidth_start >= g_iMAP_WIDTH - iClientPadding_Right)

    {

        iClientWidth_start = g_iMAP_WIDTH - iClientPadding_Right;

    }

   

     //클라이언트 끝 지점이 최대 끝지점 (맵의 길이)보다 클때

     //클라이언트 끝 지점 = 최대 끝 지점(맵의 가로길이)  

    if (iClientWidth_end > g_iMAP_WIDTH)

    {

        iClientWidth_end = g_iMAP_WIDTH;

    }

 

 

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
void CStage::Render()
{
    /*
    맵은 스크톨 처리하여, 현재 플레이어가 이동한 위치로부터 맵을 출력해줌
    출력 크기는 세로 4칸 가로 10칸으로 해줌
    0 : 벽
    1 : 길
    2 : 시작점
    3 : 도착점
    4 : 코인
    */
    CPlayer* pPlayer = CObjectManager::GetInst()->GetPlayer();
 
    int player_Coin = pPlayer->GetCoin();
 
    //플레이어의 x,y 좌표를 얻어온다
    int player_iX = pPlayer->GetX();
    int player_iY = pPlayer->GetY();
 
    // 맵의 출력은 플레이어의 위치를 중심으로 출력
    // 세로는 플레이어 2칸위부터 한칸 아래까지 출력
    // 총 4줄이 출력되는것이다
    // 가로는 플레이어 위치부터 오른쪽 10칸 까지 출력함
    constexpr int iClientPadding_left        = 0;
    constexpr int iClientPadding_Top        = -2;
    constexpr int iClientPadding_Right        = 9;
    constexpr int iClientPadding_Bottom        = 1;
 
    int iClientHeight_start = player_iY + iClientPadding_Top;
    int iClientHeight_end = player_iY + iClientPadding_Bottom;
    int iClientWidth_start = player_iX + iClientPadding_left;
    int iClientWidth_end = player_iX + iClientPadding_Right;
 
    //맵 끝에 거의 도달했을때 가로 스크롤 멈춤
    if (iClientWidth_start >= g_iMAP_WIDTH - iClientPadding_Right)
    {
        iClientWidth_start = g_iMAP_WIDTH - iClientPadding_Right;
    }
 
    if (iClientWidth_end > g_iMAP_WIDTH)
    {
        iClientWidth_end = g_iMAP_WIDTH;
    }
 
    for (int i = iClientHeight_start; i <= iClientHeight_end; ++i)
    {
        for (int j = iClientWidth_start; j < iClientWidth_end; ++j)
        {
            if (i == player_iY  && j == player_iX)
            {
                std::cout << "§";
            }
            else if (m_cStage[i][j] == SBT_WALL)
            {
                std::cout << "■";
            }
            else if (m_cStage[i][j] == SBT_ROAD)
            {
                std::cout << "  ";
            }
            else if (m_cStage[i][j] == SBT_START)
            {
                std::cout << "☆";
            }
            else if (m_cStage[i][j] == SBT_END)
            {
                std::cout << "★";
            }
            else if (m_cStage[i][j] == SBT_COIN)
            {
                std::cout << "◎";
            }
        }
        std::cout << std::endl;
    }
    std::cout << "coin : "<< player_Coin << std::endl;
    std::cout << "Left : ← " << " Right : → " << std::endl;
    std::cout << "Jump : space bar "<< std::endl;
}
 
cs

 

 

버그 해결 완료

더이상 맵의 시작지점이 보이지 않고, 횡스크롤은 정지되있다

반응형

'develop-note > MarioGame' 카테고리의 다른 글

MarioGame 최근 업데이트 2020_03_07  (0) 2020.03.07
MarioGame UML  (0) 2020.03.03
반응형

Singleton Pattern (싱글톤 패턴)

프로그램안에서 어떤 클래스의 인스턴스가 1개만 존재 하도록, 폐쇄적으로 클래스를 디자인 하는것

 

 

구현방법

1.생성자는 private 으로 막는다, 외부에서 new 를 통한 instance 생성을 할 수 없게 만든다.

 

 

 

 

2.인스턴스 하나를 담을 수 있는 포인터변수를 선언, static 으로 설정해서, 오직하나만 존재할 수 있게 만든다

 

 

 

 

 

 

3. m_pInst를 가져오거나(get), 메모리 해제(Destroy)할 수 있는 멤버 함수들을 선언, static 변수에 접근할 수 있어야 하고

외부에서 해당 함수들을 쓸 수 있어야 하므로 public 으로 지정

 

 

 

 

 

 

 

MySingletonClass.cpp

 

4. m_pInst를 초기화 해주기 위해서는 static 멤버 변수 이므로, 생성자가 아닌 클래스 외부에서 

MySingletonClass* MySingletonClass::m_pInst = nullptr; //이런식으로 초기화를 해준다

 

 

5.GetInst() 함수를 구현한다, m_pInst 가 nullptr 이라면, 새로 생성하여 instance를 초기화 해준뒤

앞으로도 호출될때마다 이미 생성된 instance를 return 하도록 한다

 

 

6.DestroyInst() 함수를 구현한다, GetInst() 함수와 유사한 구조를 가지고 있다 차이점은

delete와 nullptr, 초기화 가 return 대신에 들어간다

 

 

 

최종코드

MySingletonClass.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#pragma once
class MySingletonClass
{
private:
    MySingletonClass();
    ~MySingletonClass();
 
private:
    static MySingletonClass* m_pInst;
 
public:
    static MySingletonClass* GetInst();
    static void DestroyInst();
 
};
 
cs

 

MySingletonClass.cpp

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
#include "MySingletonClass.h"
 
MySingletonClass* MySingletonClass::m_pInst = nullptr;
 
MySingletonClass::MySingletonClass()
{
}
 
MySingletonClass::~MySingletonClass()
{
}
 
MySingletonClass* MySingletonClass::GetInst()
{
    if (!m_pInst)
    {
        m_pInst = new MySingletonClass;
    }
    return m_pInst;
}
 
void MySingletonClass::DestroyInst()
{
    if (!m_pInst)
    {
        return;
    }
    delete m_pInst;
    m_pInst = nullptr;
 
}
 
cs
반응형
반응형

Core : 매니저 클래스들 초기화, 게임 실행 준비, 소멸시, 매니저 클래스들 자원 해제

MapManager : Stage들을 초기화, Stage 선택

Stage : FileStream 객체를 사용하여, Stage 파일 읽기, 맵 로딩

FileStream : 파일 한줄씩 읽기 및 쓰기

ObjectManager : 플레이어 생성, 생성된 플레이어 인스턴스 가져오기

Player : 코인점수 관리, 플레이어 떨어졌는지, 허공에 있는지 여부 확인, 왼쪽, 오른쪽에 벽이 있는지 확인

 

지금, Stage 객체와 MapManager 객체에 맞지 않은 기능들(GameWin, GameOver)이 있다 
GameWin, GameOver 를 관리해주는 다른 매니저 클래스가 필요할 것 같다

 

 

 

 

StateManager : GameOver, PlayerWin 상태 관리 

반응형

'develop-note > MarioGame' 카테고리의 다른 글

MarioGame 최근 업데이트 2020_03_07  (0) 2020.03.07
MarioGame 스크롤 버그  (0) 2020.03.05
반응형

const

  • Lvalue로 올 수 없는 변수(상수), 주소 또는 역참조값이 Lvalue로 올 수 없는 포인터 변수등 선언할 쓰임
  • 포인터 변수를 선언할때, 자료형 앞 또는 포인터 변수 이름 앞 또는 두위치 모두 "const" keyword 를 붙이면, 각각의 위치에 따라서, 값또는 주소 혹은 둘다, Lvalue로 올 수 없음, 대입 연산자 왼쪽에 올 수 없음
  • 이때 주의해야 될것은, 어떤 특정 변수를 대상으로 하는게 아니라, 자료형, 자료형* 포인터  를 대상으로 하는 것이다, 즉 const int a 가 있다면, int a를 const 로 만든게 아니라, a 가 const int 타입이며, 다른 const 자료형 처럼 Lvalue로 올 수 없는 것이지 값을 수정할 수 없는게 아니다.
  • constexpr과는 다르게, 값이 컴파일타임 뿐만 아니라 런타임에도 값이 정해질 있음, 둘다 허용
    • 선언과 동시 초기화를 하면 컴파일타임(Compile-time)

 

  • 선언  런타임에 초기화를 하면 런타임(Run-time)

 

 

 

const 자료형 변수식별자 = 초기화값;

값   수정   불가능

 

 

const 자료형& 레퍼런스식별자 = 변수;

레퍼런스   변수   역시   동일 ,  값을   수정할   수   없음

 

 

 

 

포인터 변수에 붙일 경우

1. const 자료형 *포인터식별자 = &변수;

자료형 앞에 붙는 경우 역참조값 수정 불가능

 

2. 자료형 * const 포인터식별자 = &변수;

포인터 변수 식별자(이름) 앞에 붙는 경우,역참조값 수정가능, 가리키는 주소 수정 불가능

 

 

 

3. const 자료형 * const 포인터 식별자 = &변수

자료형앞 ,  포인터 변수 식별자(이름)앞, 역참조값, 가리키는 주소,  둘 다 수정 불가능

 

또한 멤버 함수 뒤에 const 붙일경우, 해당 함수는 멤버 변수를 수정할 없다

반응형
반응형

Local Variable(지역변수)

  • 함수 안에서만 사용, 함수에서 선언되고 초기화되며 삭제됨
  • 다른 파일에서 참조할 일이 없으므로, Linkage 필요 없음
  • Stack Segment 에 저장됨

 

Global Variable(전역 변수)

  • 프로젝트 전체에서 사용, 프로그램이 종료 될때까지 존재함
  • 가급적이면 사용을 권장하지 않음
  • Data Segment에 저장됨
  • 장점 : 간단한 코드를 작성할때는 유용함
  • 단점 : 여러곳에서 초기화 있기때문에, 이도 저도 못하는 상황이 있음, 디버깅이 어려워짐

 

주의

  • 변수 선언과 정의를 따로 분리 하지 않고, 헤더파일에 선언과 동시 초기화를 하게 되면 문제가 발생함, 하나의 전역 변수(constexpr, 상수) 여러 .c 파일에서 사용할 경우, .c 파일 개수 만큼 각기 다른 메모리주소를 갖고있는 전역변수가 생김

 

ex1)static const int 전역 변수를 선언한뒤, 각기 다른 .c 파일에서 참조하도록함

 

3개의 함수에서 같은 이름의 전역변수 주소를 확인 했을때 각각 다른 주소를 보임,

 

 

올바른 방식의 전역변수 선언 정의

헤더파일에 선언만 하고, .c 파일에 선언및 정의를 넣어주면 된다

 

결과

 

Static Local Variable(정적 지역 변수)

  • 선언된 함수에서만 사용, 외부에서 접근 불가능
  • 오직 하나만 존재하는 변수
  • 선언과 동시에 반드시 초기화가 이뤄져야함 그리고 한번만 하게됨
  • 함수내에 static 변수를 선언과 동시 초기화를 하면, 나중에 다시 함수를 호출했을때, 초기화를 반복 하지 않음
  • Data Segment에 저장됨

 

 

Static Global Variable(정적 전역 변수)

  • 파일( *.c )안에서만 사용, 파일 외부에서 접근 불가능
  • 반드시 헤더 파일이 아닌, .c파일에서 선언및 초기화를 해야함
  • 오직 하나만 존재하는 변수
  • Data Segment에 저장됨

 

 

 

반응형

'C' 카테고리의 다른 글

memset, memcpy, memmove  (0) 2020.03.13
expression(표현식)  (0) 2020.03.12
fseek, ftell, 파일 크기 알아내기  (0) 2020.02.25
FileStream Input Output  (0) 2020.02.22
반응형

Static Function (정적 함수)

  •  .cpp 파일내에 선언 정의가 함께 있으며, .cpp 파일내에서만 사용가능

 

Static Member Function (정적 멤버 함수)

  • C++ 에서는 클래스의 멤버 함수또한 정적으로 선언 가능
  • static member function 클래스의 객체를 생성하지 않고도클래스 이름만으로 호출 가능
  • 객체를 생성하지 않으므로, this 포인터를 가지지 않음
  • 특정 객체와 결합하지 않으므로, 정적 멤버 변수 밖에 사용할 없음ex1)

static 멤버 변수의 초기화는 클래스 외부에서 한다

위 그림에서 보이는것처럼 nonstatic member function 에서는 비정적멤버 변수, 정적 멤버변수 둘다 접근 가능하다.

그러나

"비 정적 멤버 참조는 특정 객체에 상대적 이어야 합니다"

 Static Member Function(정적 멤버 함수) 에서는, 비정적멤버 변수에 접근할려고 할경우 에러가 뜬다

따라서 오직 정적 멤버 변수(static member variable)만 접근이 가능하다

 

또한 private 인경우, static 이라 할지라도, 클래스 외부에서 사용 불가능하다

private 로 바꿨더니 호출이 불가능하다

 

private:

static member function

클래스 스코프 내부에서만 사용가능

public:

static member function

클래스 스코프 내부 외부에서

객체 생성하지 않고 사용가능

static function

.cpp 파일 내에서만 사용가능, 선언과 정의
둘다
.cpp 있어야함

 

반응형

'C++' 카테고리의 다른 글

c++ Singleton Pattern 기본 구현 방법  (0) 2020.03.03
const  (0) 2020.02.29
CopyConstructor, Shallow Copy, Deep Copy  (0) 2020.02.24
std::function (c++ 11)  (0) 2020.02.23
Input Output library  (0) 2020.02.21
반응형

fseek

(스트림 위치 지정자 이동)

C

 #include <stdio.h>

C++

 #include <cstdio>

 

fseek(FILE* fileStream, long int offset, int origin);

  • origin 인자로 전달된 위치로 부터 offset 만큼 더해진 위치로 위치 지정자(poistion indicator) 이동시킴

 

origin 인자로 들어가는 매크로 상수들이 존재한다, 해당 위치로 이동하고 싶으면

fseek(pFile, 0, SEE_SET); // 시작위치로 이동

처럼 호출하면 된다

 

 

SEEK_SET

파일의 시작

SEEK_CUR

현재 파일 포인터의 위치

SEEK_END

파일의

 

 

주의 : 텍스트 파일에 fseek 함수를 사용할때, offset 인자로 0 아닌 혹은 ftell 함수에 의해 반환된 값을 사용할 때에는 일부 플랫폼에서는 문제가 생겨서, 예상치 못했던 위치로 이동할 있음

 

 

 

 

ftell

(스트림 위치 지정자의 현재 위치를 알려줌)

 

C

 #include <stdio.h>

C++

 #include <cstdio>

 

 long int ftell(FILE* stream);

  • fseek함수와 같이 써서 파일의 크기를 알아내는데 사용한다
  • 이진(binary) 스트림의 경우, 리턴된 값은 파일의 시작 부분에서 현재위치 까지의 바이트 수를 말함
  • 텍스트(txt) 스트림의 경우, 보이지 않는 문자( Line Feed , Carriage Return, ...) 등등도 포함한 바이트 수가 나오게 된다
    • LF : Line Feed, 현재 위치에서 바로 아래로 이동 \n
    • CR : Carriage Return,  커서의 위치를 앞으로 이동 \r

 

fseek + ftell 파일 크기 알아내기

 

ex1)

반응형

'C' 카테고리의 다른 글

memset, memcpy, memmove  (0) 2020.03.13
expression(표현식)  (0) 2020.03.12
Local Variable, Global Variable, Static Variable  (0) 2020.02.28
FileStream Input Output  (0) 2020.02.22
반응형

Copy Constructor (복사 생성자)

자기 자신(객체)과 같은 자료형의 instance 매개변수로 받으며 

매개변수로 받는 instance의 데이터들을 복사해서 그대로 자기자신(객체)의 데이터들에 대입하는 생성자 함수

 

어떤 클래스를 디자인하는데, Copy Constructor(복사생성자)를 구현해 놓지 않고

 

A a;

A a_2 = a;

 

위와 같이 copy initialization 경우, 컴파일러가 자동으로 default 복사 생성자를 만들어 준다

 그러나 default copy constructor Shallow Copy 동작하므로 유의하며 쓰던지, copy contructor assignment operator 따로 구현하여, 그안에서 deep copy 되도록 하여야 한다

 

 

Shallow Copy (주소값도 그대로 복사)

객체를 복사할때, default 복사 생성자를 이용할 경우, 객체안의 멤버 변수들이 전부 복사 된다 이때

변수들이 Primitive types(int, char, bool, float, double, 값형) 경우, 값들을 그대로 복사해도 문제가 되지 않는다, 그러나 class struct, 포인터 변수, 등등 객체 또는 메모리의 주소를 가리키는 경우는, 가리키는 메모리 주소를 그대로 복사하는 꼴이 되버려서, 원본 또는 복사본 둘중 하나가 delete 경우, 다른 한쪽에도 똑같은 영향을 미치는 것이다, 왜냐하면 서로 같은 주소를 바라보고 있기 때문이다

 

ex1)

 

 

코드를 보면 hello 라는 객체를 선언 한뒤 "hello" 생성자를 통해 초기화 했다

copy 라는 객체를 선언하고 hello copy initialize 했는데, 이때 copy constructor 구현되있지 않기 때문에 컴파일러는 default copy constructor 구현하여 실행시킨다

그리고 값만을 복사하는 shallow copy 진행된다

만약 primitive type 이라면 상관 없지만, 주소를 대입해야 하는 포인터

타입 이라면, 주소를 복사해버리게 된다

아래와 같이 같은 주소를 공유하게 되는 사태가 벌어지는 것이다

 

copy.m_Data = hello.m_Data;

 

 

Debug 위해서 코드를 좀더 추가해서 다시 출력해보면

역시나 둘의 주소가 같다는걸 있다.

 

 

이렇게 되면, hello.m_Data 또는 copy.m_Data 둘중 하나가 delete 되면

다른 쪽에서도 delete 된것과 같은 것이다

scope 벗어나게 되면서 copy 자동으로 소멸되고, 소멸자를 실행해

delete[] copy.m_Data; 실행해 delete[] hello.m_Data; 실행한 것과 같은 결과가 나오고

hello.GetString(); 통해 hello.m_Data 접근하여도 이미 delete 되어 메모리 반환 되었기 때문에 쓰레기 값이 나오게 된다

 

정리하자면 멤버 변수로 포인터 또는 어떤 객체의 주소를 참조하는 경우

default contructor 사용금지

Shallow Copy(값만 그대로 복사) 금지

Deep Copy 해야한다

 

 

 

 

 

Deep Copy

(주소를 새로 할당하고 새로 할당된 객체에 값들을 복사)

class struct, 포인터 변수, 등등 객체 또는 메모리의 주소를 가리키는 경우는, 가리키는 메모리 주소는 복사 하지 않고 새로 동적할당을 한다, 그리고 원본의 주소가 가리키는 값들을 새로 할당된 객체에 복사한다, copy constructor 대입연산자를 따로 구현하여 default constructor 실행되지 않게 하는 것이다

 

 

ex1) Copy Constructor 구현

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
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <iostream>
#include <cassert>
 
class MyString
{
private:
    char* m_Data = nullptr;
    int m_length = 0;
 
public:
    MyString(const char* _source = "")
    {
        assert(_source);
 
        m_length = std::strlen(_source) + 1;
        m_Data = new char[m_length];
 
        strcpy_s(m_Data, m_length, _source);
        m_Data[m_length - 1= '\0';
    }
//Copy Constructor
    MyString(const MyString& _source)
    {
        std::cout << "User defined Copy Constructor called" << std::endl;
        m_length = _source.m_length;
        if (_source.m_Data != nullptr)
        {
            if (m_Data)
            {
                delete[] m_Data;
                m_Data = nullptr;
            }
            //Deep copy
            m_Data = new char[m_length];
            strcpy_s(m_Data, m_length, _source.m_Data);
            m_Data[m_length - 1= '\0';
        }
        else
        {
            m_Data = nullptr;
        }
    }
 
    ~MyString()
    {
        delete[] m_Data;
    }
 
    char* GetString() { return m_Data; }
    char& GetDataRef(){ return *m_Data; }
    int GetLength() { return m_length; }
};
 
int main()
{
    MyString hello("hello");
    {
        MyString copy = hello;
        char& helloDataRef = hello.GetDataRef();
        char& copyDataRef = copy.GetDataRef();
        const char* result = &helloDataRef == &copyDataRef ? "true" : "false";
        printf("Copy's &m_Data == hello's &m_Data : %s\n", result);
        printf("Copy's &m_Data : 0x%p\n"&helloDataRef);
        printf("hello's &m_Data : 0x%p\n"&copyDataRef);
    } 
 
    std::cout << hello.GetString() << std::endl;
}
cs

 

결과는

 

 

사용자가 정의가 Copy Constructor 호출됐으며

hello, copy, 각각의 데이터 주소가 별개의 주소를 가리키고 있으며

copy 소멸되었어도 hello.m_Data delete 된것은 아니기 때문에

쓰레기 값이 아닌 온전한 데이터가 출력이 된다

 

그러나 Shallow Copy

선언후 동시 초기화 뿐만 아니라

초기화만 하는 상황

 

 

코드와 같이 같은 타입의 객체를 다음 줄에 대입할때도 실행이 된다

 

MyString str2; //선언

str2 = hello; //초기화, 같은 타입 객체 대입

 

이때는 Copy Constructor 구현해놨다 하더라도 에러가 발생한다

따라서 아래처럼 대입연산자(Assignment operator)를 구현해야 한다

 

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <iostream>
#include <cassert>
 
class MyString
{
private:
    char* m_Data = nullptr;
    int m_length = 0;
 
public:
    MyString(const char* _source = "")
    {
        assert(_source);
 
        m_length = std::strlen(_source) + 1;
        m_Data = new char[m_length];
 
        strcpy_s(m_Data, m_length, _source);
        m_Data[m_length - 1= '\0';
    }
 
    MyString(const MyString& _source)
    {
        std::cout << "User defined Copy Constructor called" << std::endl;
        m_length = _source.m_length;
        if (_source.m_Data != nullptr)
        {
            if (m_Data)
            {
                delete[] m_Data;
                m_Data = nullptr;
            }
            //Deep copy
            m_Data = new char[m_length];
            strcpy_s(m_Data, m_length, _source.m_Data);
            m_Data[m_length - 1= '\0';
        }
        else
        {
            m_Data = nullptr;
        }
    }
 
    //Assignment Operator
    MyString& operator = (const MyString& _source)
    {
        std::cout << "Assignment operator" << std::endl;
        //
        //if (this == &_source) //prevent self-assignment
        //    return *this;
 
        if (m_Data)
        {
            delete[] m_Data;
            m_Data = nullptr;
        }
 
        m_length = _source.m_length;
 
        if (_source.m_Data != nullptr)
        {
            m_Data = new char[m_length];
            strcpy_s(m_Data, m_length, _source.m_Data);
            m_Data[m_length - 1= '\0';
            return *this;
        }
        else
        {
            m_Data = nullptr;
            return *this;
        }
 
    }
 
 
 
    ~MyString()
    {
        delete[] m_Data;
    }
 
    char* GetString() { return m_Data; }
    char& GetDataRef(){ return *m_Data; }
    int GetLength() { return m_length; }
};
 
int main()
{
    MyString hello("hello");
    {
        MyString copy; 
        copy = hello;
        char& helloDataRef = hello.GetDataRef();
        char& copyDataRef = copy.GetDataRef();
        const char* result = &helloDataRef == &copyDataRef ? "true" : "false";
        printf("Copy's &m_Data == hello's &m_Data : %s\n", result);
        printf("Copy's &m_Data : 0x%p\n"&helloDataRef);
        printf("hello's &m_Data : 0x%p\n"&copyDataRef);
    } 
 
    std::cout << hello.GetString() << std::endl;
}
cs

 

 

//Assignment Operator 부분을 추가 시키면 다시 정상 작동된다

 

 

그런데 대입연산자(Assignment Operator) 구현할때 주의할 점은

 

 

if (this == &_source) //prevent self-assignment

return *this;

 

위코드를 반드시 붙여줘야 한다, 자기 자신을 복사하게 만드는

hello = hello;

막는 것이다, 자기 자신을 Rvalue 놓고 Lvalue 자기자신에게

대입하게 되면 Lvalue 원래 있던 자기 자신의 데이터를 지울 것이고

지우게 되면 Rvalue 데이터도 지워져서 결국 지워지는 효과가 나올 있다

(물론 의도하던 안하던, 애초에 hello = hello; 지우는 코드로 쓰는 사람은

없을 것이고, 어떤 결과가 나올지 예측하기도 힘들다)

 

어쨌든 Copy Constructor, Assignment Operator 둘다 구현해 놓는 것을 권장한다

반응형

'C++' 카테고리의 다른 글

c++ Singleton Pattern 기본 구현 방법  (0) 2020.03.03
const  (0) 2020.02.29
Static Member Function(정적 멤버 함수)  (0) 2020.02.27
std::function (c++ 11)  (0) 2020.02.23
Input Output library  (0) 2020.02.21
반응형

std::function (c++ 11)

#include <functional>

c++11 부터 지원됨

함수 포인터 변수 역할을 해줌

 

 

 

사용법

 

//function 객체 선언

std::function<함수리턴타입(인자타입)>  function객체식별자;  

 

function객체식별자 = std::bind(함수이름);

 

ex1)

 

 

위와 같은 방식으로 사용하면 되고

 

 

멤버 함수를 초기화 호출할떄는

 

//function 객체 선언

std::function<함수리턴타입(인자타입)>  function객체식별자;  

 

 

//초기화

function객체식별자 = std::bind(&클래스식별자::함수이름, 인스턴스);

 

function객체식별자(); //호출

 

ex2)

 

 

 

ex3) 또한 인자가 있는 경우 placeholder 있다

이때 반드시 인자 타입이 일치해야 한다

 

ex4) 인자와 리턴이 있는 멤버 함수

 

반응형

'C++' 카테고리의 다른 글

c++ Singleton Pattern 기본 구현 방법  (0) 2020.03.03
const  (0) 2020.02.29
Static Member Function(정적 멤버 함수)  (0) 2020.02.27
CopyConstructor, Shallow Copy, Deep Copy  (0) 2020.02.24
Input Output library  (0) 2020.02.21

+ Recent posts