#include <iostream>
class MyInt
{
public:
int operator+(int _Value)
{
}
};
class Sword
{
};
class Player
{
public:
// Sword Weapon; // 플레이어와 검은 하나로 붙어있다 (정적)
Sword* Weapon = nullptr; // 플레이어는 아직 검을 들고 있지 않다 (동적)
void CreateSword()
{
Weapon = new Sword(); // 플레이어가 검을 만들어 든다 (동적)
}
void DeleteSword() // new를 만들었다면 delete도 반드시 만들자!
{
delete Weapon;
}
};
int main()
{
_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);
{
int Value = 0;
/*
위치 : 200번지 (스택영역)
크기 : 4byte
형태 : int
값 : 0
*/
Value = 20; // 스택영역의 200번지에 코드영역의 20이라는 값을 넣으라는 뜻
int* Ptr = &Value;
/*
위치 : 220번지 (스택영역)
크기 : 8byte
형태 : int*
값 : 200번지
*/
Ptr = nullptr; // 220번지를 nullptr로 하라는 뜻
*Ptr = 1000; // 200번지에 1000을 넣으라는 뜻
}
{
MyInt NewInt = MyInt();
NewInt.operator+(/*&NewInt => this, */10);
new int(10); // operator new(sizeof(int); => (생략 O)
// reinterpret_cast<int*>(operator new(sizeof(int))); => (생략 X)
// 실제론 위와 같은 함수 형태의 연산자이다
// 스택영역에서 operator new 실행 -> 힙영역의 4byte를 사용하겠다
// 하지만 리턴값을 받지 않으면 무조건 leak이 되어버린다
// 해당 메모리에 접근할 수 없기 때문이다
// int();, Player(); => (X)
// int Value = int();, Player NewPlayer = Player(); => (O)
new Player(); // operator new(sizeof(Player));
new int[10]; // operator new(sizeof(int) * 10);
int* Ptr = new int(10);
/*
Ptr
위치 : 1000번지 (스택영역 main함수)
크기 : 8byte
형태 : int*
값 : 500번지
???
위치 : 500번지 (힙영역)
크기 : 4byte
형태 : int
값 : 10
*/
}
{
// 동적 할당으로 소유의 개념을 표현할 수 있게 되었다
Sword NewSword;
Player NewPlayer;
NewPlayer.Weapon = &NewSword;
}
}
#include <iostream>
class Player
{
public:
int Hp;
void MoveMsg()
{
printf_s("플레이어가 움직입니다.");
// this -> Hp; => this가 nullptr일 경우 에러
}
};
void MyDelete(int* _Ptr)
{
if (nullptr == _Ptr) // safe delete 형식 (4)
{
return;
}
_Ptr = nullptr;
// 이렇게 적어도 함수 밖의 포인터에 영향을 줄 수 없다 (3)
}
int main()
{
_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);
// !동적 할당 주의사항!
{
// [1. 이미 동적 할당한 곳에 다시 동적 할당 하지 말자]
int* Ptr = new int(); // 위치 : 500번지
Ptr = new int(); // 위치 : 600번지 => 500번지에 접근할 방법 잃어버림
delete Ptr; // 600번지 delete
// 4byte leak 발생
// 포인터는 한번에 하나만 가리킬 수 있다
// delete는 하나만 삭제해준다
}
{
// [2. 포인터를 사용할 때에는 반드시 방어코드를 치자]
// null reference exception
int* Ptr = nullptr;
*Ptr = 200; // 0번지에 200을 넣어라...?
// nullptr 체크
if (Ptr != nullptr)
{
*Ptr = 10;
}
// nullptr은 사용할 수 없다
// int* Ptr = new int() 이런 식으로 동적 할당을 해주었더라도, 방어코드를 적자
// 자기가 만든건 자기가 테스트해보는 습관을 들이자
Player* NewPlayer = nullptr;
NewPlayer->MoveMsg(/*NewPlayer => this*/); // 가능
// Q) NewPlayer가 null을 가리키고 있는데 왜 에러가 나지 않나?
// MoveMsg 함수 내부에서 this를 사용하지 않았기 때문에 에러가 나지 않는다
// 염려된다면 객체를 사용하기 전에 아래와 같은 방어코드가 실행되도록 적어두자
if (NewPlayer == nullptr)
{
return;
}
}
{
// [3. delete한다고 해도 포인터가 nullptr가 되지는 않는다]
// dangling pointer
int* Ptr = new int(); // 500번지(힙영역)을 가리킴
delete Ptr; // 500번지의 내용 삭제
// 하지만 Ptr은 여전히 500번지 또는 힙영역의 어딘가를 가리키고 있다
int* Ptr = new int();
MyDelete(Ptr); // delete Ptr; 과 동일한 의미
// write access violation
if (Ptr != nullptr)
{
*Ptr = 30;
// Ptr은 여전히 힙영역의 허공을 가리키고 있기 때문에 작동은 하지만
// 가리키고 있는 대상이 없기 때문에 에러가 난다
}
// 따라서 반드시 아래와 같은 코드를 함께 쳐줘야 한다
Ptr = nullptr;
}
{
// [4. delete nullptr 하지 말자]
int* Ptr = nullptr;
delete Ptr; // 불가능
// ...은 아니고 operator delete(Ptr) 함수가 알아서 막아주기는 한다
// safe delete 코드
if (Ptr != nullptr)
{
delete Ptr;
Ptr = nullptr;
}
// 기본적으로 nullptr을 delete하는 경우를 막기 위해
// 위와 같은 코드를 꼭 추가해주자
}
{
// [총정리]
int* Ptr = new int();
if (Ptr == nullptr) // nullptr 체크
{
return;
}
*Ptr = 30;
if (Ptr != nullptr) // safe delete 코드
{
delete Ptr;
Ptr = nullptr;
}
}
}
💡 게임은 크게 두가지 요소로 구성되어 있다
엔진 요소 => 물리, 중력, 디버깅, 빛, 렌더링, 메쉬, 이미지
컨텐츠 요소 => 플레이어, 몬스터, 블록, 게임 규칙이 중 엔진 요소는 다른 게임을 제작할 때 재활용될 가능성이 높다.
이를 위해 재사용될만한 부분을 모아 프로젝트 단위로 관리한다. ⇒ 라이브러리
e.g. Galaga/Galaga.cpp에서 ConsoleEngine/ConsoleScreen.h 사용하기
엔진 프로젝트 = ConsoleEngine
컨텐츠 프로젝트 = Galaga
컨텐츠 프로젝트 ‘포함 디렉터리’ 추가
Galaga 속성 → 모든 구성, 모든 플랫폼 → VC++ 디렉터리 → 포함 디렉터리 → ..\;
추가 → 적용 → Galaga.cpp에 #include <ConsoleEngine/ConsoleScreen.h>
추가
컨텐츠 프로젝트 ‘참조’ 설정
Galaga 참조 → ConsoleEngine 추가 → 확인
엔진 프로젝트 ‘라이브러리 프로젝트’ 설정
ConsoleEngine 속성 → 모든 구성, 모든 플랫폼 → 일반 → 구성 형식 → 정적 라이브러리(.lib) → 적용
솔루션의 ‘프로젝트 종속성’ 설정
솔루션 속성 → 프로젝트 종속성 → 프로젝트 : Galaga → 다음에 종속 : ConsoleEngine
제대로 작동하는지 테스트하기
// [ConsoleEngine/ConsoleScreen.h]
#pragma once
class ConsoleScreen
{
public:
void Test();
};
// [ConsoleEngine/ConsoleScreen.cpp]
#include "ConsoleScreen.h"
#include <iostream>
void ConsoleScreen::Test()
{
printf_s("라이브러리 테스트");
}
// [Galaga/Galaga.cpp]
#include <ConsoleEngine/ConsoleScreen.h>
int main()
{
ConsoleScreen NewScreen = ConsoleScreen();
NewScreen.Test(); // 라이브러리 테스트
}