본문 바로가기
Win32 API/기초

Win32 API 기초 : Core 클래스 (1) / Singleton (1)

글: 시플마 2024. 6. 23.

프로그램을 만들기 앞서, 

Win32 API 프로젝트 생성 시 자동으로 

만들어진 파일은 Default라는 새 필터를 생성하여 넣고

 

Engine이라는 필터를 만들어서 

그 안에 Header 필터와  Core 필터를 만들어 주겠습니다.

 

그리고 클래스 마법사를 통해 

CCore라는 클래스를 생성하겠습니다.

 

 

이는 프로그램에서 중추 역할을 하는 클래스입니다.

정확히는 CCore라는 클래스로 만든 객체가

프로그램의 중추 역할을 하는 것이겠죠?

 

그래서 CCore로 만든 객체는 한 개만 존재해야 할 겁니다.

 

이를 위해 싱글톤 패턴을 적용하여

객체의 생성을 1개로 제한할 겁니다.

 

 


 

일단 생각해 볼 수 있는 건

 

생성자를 private로 선언하는 것이죠.

 

객체가 만들어지려면 반드시 생성자를

호출을 통해 만들어지는데 private로 선언하게 되면

어떤 파일에서든 객체를 함부로 만들지 못하겠죠.

 

근데 이렇게 되면 객체를 한 개만 만들 수 있는 게 아니라

하나도 만들지 못하는 거죠?

 

 

그래서 객체를 만들 수 있도록

 

멤버 함수를 하나 만들었습니다.

 

GetInstance라는 멤버 함수를 통해

new 키워드로 Heap 영역에 객체를 생성하고

해당 객체의 주솟값을 반환하려고 합니다.

 

 

근데 모순이 발생하죠. CCore 클래스의 멤버 함수를 호출하려면

결국 CCore 클래스로 만든 객체가 있어야 한다는 것이죠.

 

만약 아래 코드와 같은 클래스가 있을 때

 

func 함수를 호출하면

func 함수를 호출한 객체의 멤버 i의 값에

0이 대입될 겁니다. 이런 게 가능한 이유는

멤버 함수를 호출할 때에는 객체의 주솟값을 this에 갖고 있어

어떤 객체의 멤버 함수가 호출될지 확실히 알 수 있습니다.

 

그러나 이러한 객체 자체가 존재하지 않는다면 

어떤 객체의 주솟값에 접근하여 멤버 함수를 호출해야 할지 알 수가 없죠.

 

 

객체 없이 멤버 함수에 접근하려면

static 키워드를 사용하면 됩니다.

 

이러면 객체 없이도 범위 지정 연산자를 통해 

멤버 함수 FUNC를 호출할 수 있게 되죠.

 

대신 static 멤버 함수는 this가 존재하지 않도록 

설계되어 있어 멤버 변수에는 접근할 수 없습니다.

 

this가 없다는 것은 어떤 객체인지 알 수 없다는 것이고

그러면 멤버 i가 어떤 객체의 것인지 알 수 없다는 의미니까요.

 

그럼 c.FUNC(); 이런 식으로 객체를 통해 접근한 경우는

this가 존재할까요? 여전히 존재하지 않습니다

 

 

객체 없이 호출이 가능한 이유는 static 키워드를 붙이는 순간

해당 함수 또는 변수는 data 영역에 생성되기 때문입니다.

 

static 변수가 함수 안에 선언되어 있을 때 해당 함수 안에서만

접근이 가능합니다. 또한 함수가 호출되고 종료되어도

static 변수는 data 영역에 존재하므로 사라지지 않습니다.

 

static 변수가 파일 안에 선언되어 있다면 전역 변수와는 다르게

각자 다른 cpp 파일이라면 같은 이름으로 선언할 수가 있죠.

어차피 선언된 파일에서만 접근이 가능하기 때문입니다.

 

static 변수가 클래스 안에 선언되었다면 이를 정적 멤버라고 부릅니다.

정적 멤버도 data 영역에 존재하므로 클래스를 통해 객체를 여러 개

만들어도 정적 멤버는 오직 하나만 존재하며 객체를 해제해도

여전히 존재하겠죠.

 

 

다시 CCore 클래스로 돌아와서 

 

객체를 생성하기 위한 GetInstance 함수를

static 멤버 함수로 선언하였습니다. 이러면 객체 없이

해당 함수를 호출할 수 있을 겁니다.

 

해당 함수를 호출하면 

static으로 선언된 CCore형 포인터 pCore가

일단 nullptr을 가리킵니다.

 

static으로 선언되었으므로 해당 함수 안에서만

접근이 가능하며 정적으로 선언된 지역 변수는

한번 초기화가 진행되면 이후에는

초기화 구문을 만나도 초기화가 진행되지 않습니다.

 

만약 pCore가 아무것도 가리키고 있지 않다면

heap 영역에 공간을 할당하여 그 공간(객체)을 가리킵니다.

 

만약 pCore가 이미 어떤 공간(객체)을 가리키고 있다면 

아무런 동작을 하지 않습니다. 덕분에 해당 함수를 여러 번

호출하여도 처음 호출할 때 딱 한 번 객체를 만들고 

이후에는 아무런 일도 발생하지 않겠죠.

 

그리고 해당 공간(객체)을 가리키는 포인터 pCore를 반환합니다.

 

 

 

 


 

 

 

 

이제 new 키워드를 통해 할당한 heap 영역이

더 이상 필요가 없다고 판단되면 메모리 해제를 해야겠죠?

 

그래서 Release라는 멤버 함수를 만들었습니다.

마찬가지로 객체 없이 호출하기 위해 static으로 선언하였습니다.

 

GetInstance 함수를 호출하여 반환된,

pCore가 갖고 있는 heap 영역의 주솟값을 

CCore형 포인터 p에 대입합니다.

 

그리고 delete 키워드를 통해 p가 가리키는

공간의 메모리를 해제합니다.

 

 

근데 만약 메모리를 해제하였는데

다시 할당하려는 상황을 가정해 봅시다.

 

일단 Release 함수가 호출된 후

동작을 완료한 시점의 상황은 위 그림과 같을 겁니다.

 

성공적으로 heap 영역에 할당된 메모리를 해제하였죠.

 

이 상태에서 CCore 클래스로 만든 객체를 다시 

생성해야 해서 GetInstance 함수를 다시 호출하였습니다.

 

그럼 10번째 줄에 있는 if 문에 걸릴까요?

 

포인터 pCore가 가리키는 공간은 비록 메모리 해제되었지만

해당 메모리의 주솟값은 계속 들고 있는 상태입니다.

 

그래서 10번째 줄에 있는 if 문에 걸리지 않고

GetInstance 함수는 아무런 동작도 하지 않은 채

pCore를 반환하게 되겠죠. 실질적으로 객체가 생성되지 않는 것입니다.

 

 

이 문제를 해결하려면 GetInstance 함수에서도,

Release 함수에서도 똑같은 정적 변수를 다루어야 할 겁니다.

 

그래서 정적 멤버로 g_pInst를 추가하였고 CCore.cpp 파일에서

 

nullptr로 초기화하였습니다.

 

nullptr로 초기화하였으므로

GetInstace 함수가 정상 작동할 것입니다.

 

이후 Release 함수를 통해

heap 영역에 할당된 객체를 가리키는

포인터 g_pInst가 nullptr이 아니면,

즉 어떤 객체를 가리키고 있으면 그 객체의

메모리를 해제하고 포인터 g_pInst가 

nullptr을 가리키게 합니다.

 

이렇게 되면 메모리 해제가 진행된 이후

다시 CCore 클래스로 만든 객체를 할당하여

가리키려고 할 때, g_pInst는 nullptr을 가리키게 하였으므로

정상적으로 heap 영역의 메모리를 다시 할당하게 될 겁니다.

 

 

 


강의 출처 : https://www.youtube.com/watch?v=MJ31abxA7A0&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=7