싱글톤(Singleton) 패턴 구현

Yama·2024년 1월 21일
0

어소트락 수업

목록 보기
51/55
  • 이제부터는 우리의 오브젝트들이 뛰어놀수 잇는 기반을 만들어야한다.

    • 우리가 게임 코드를 만들다 보면 하나하나의 CEntity객체들은 무수히 많이 쏘아지겠지만 저 객체들을 관리해주는 관리자 기능은 딱 하나만 존재해야한다.

    • 물체들의 맵을 관리(게임레벨)을 관리하는 놈이 있어야 한다.

  • 프로그램 전체를 담당하는 관리자가 있어야한다.

    • 관리자 역할을 하는 클래스를 만들어서 그 객체를 하나만 만들어서 관리할것이다.
  • 저 클래스의 명시해둔것을 실제로 하는것은 해당 객체기 때문에 우리가 CEntity클래스를 만들어서 CEntity객체를 엄청나게 많이 만든 것처럼

  • CEO라는 자료형을 설계하고 객체를 만들어서 그 만들어진 객체가 그걸 담당할것인데 문제는 CEO객체를 하나까지만 생성되게 제한해야한다는 것.

    • 1클래스 1객체 매칭되어야하는 상황이 많이 존재한다.

디자인 패턴(설계 유형)

  • 프레임 워크틀을 설계하다보면 공통되는 패턴이 등장한다.

    • 다른 프로그래머도 비슷한 유형으로 해결했다.
  • 설계유형들을 정리해놓은게 디자인 패턴이라고 부른다

    • iterator패턴, 싱글톤 패턴, 프레임워크 패턴
  • 언어에 국한되는 개념이 아니다.

  • 패턴은 동일한 경우고 우리는 C++을 이용해서 하고 있기 떄문에 싱글톤 패턴으로 C++로는 어떻게 해결할가라는 방식으로 접근해야한다.

싱글톤 패턴

  • 클래스로 생성되는 객체를 1개로 제한한다.
    • CEngine클래스로 할것이고 따라서 CEngine클래스는 하나의 객체만 만들어지게 제한해야한다.
  • 관리자급 객체를 설계할때 자주 발생하는 패턴이다.
class CEngine
{
private:

public:
};
  • CEngine클래스의 기본틀
// main.cpp
    CEngine engin1; // 가능
    CEngine engin1; // 불가능
  • 지금은 엔진이라는 클래스의 1개 까지만 만들고 2개부터는 못만들게 해줘야 한다.
private:
	CEngine();
  • private필드에 생성자를 만들어버린다면 외부에서 객체를 만들지못한다.
    • 객체가 만들어질떄마다 생성자가 있어야 되서.
    • 이렇게 해버리면다면 객체가 1개도 못만들고 한개도 못만든다.
  • private도 클래스 내부에서는 접근가능하니 이 방법을 차감해서 1개만 만들수 있게 구현을 하자.
// 3
private:
	static CEngine* pEngine;
public: 
	CEngine* GetEngine()
    {
    	// 1
    	CEngine engine;
        return &engine;
        // 2
        CEngine* pEngine = new CEngine;
        return pEngine;
        
    }
  • 객체를 1개 만들어서 반환해주는 함수를 구현.

    • 1번처럼 지역변수로 만들어도 함수가 끝나면 날라가고

      • 1번처럼 engine주소에 접근을해도 이미 사라진 후다.
    • 2번은 동적할당해서 주는 방식이다.

      • 매번 달라할때마다 객체를 계속 해서 준다.

      • 외부에서는 접근이 안되지만 내부에서 접근이 가능하다.

  • 계속 생성이 되면 안되니까 포인터 변수를 하나 선언해줘야한다.

    • 3번처럼 정적 맴버 선언을 포인터로 둬서 데이터영역에 둔다
// CEngine.cpp 내부
 CEngine* CEngine::pEngine = nullptr;
  • .cpp파일에 구현(초기화)를 했다.
	CEngine* GetEngine()	
	{		
		if(nullptr == pEngine)
		{
			pEngine = new CEngine;
		}
		return pEngine;
	}
  • 3번에서의 주소값을 받아두면 객체의 주소가 nullptr이면 새로운 객체를 만들게 해준다.
    • 2번쨰 호출부터는 막는다.
    • 2번째 호출부터는 그래서 첫번쨰 주소값을 계속 준다.
// CEngine.h의 내부
public:
	~CEngine();
  • 소멸자 선언
// CEngine.cpp내부
CEngine::~CEngine()
{}
  • 소멸자 구현부
  • 힙 메모리 영역에다가 생성을 했기 때문에 그대로 프로그램 종료되면 힙메모리에 생성된 객체는 절대로 수동으로 사라지지않늗다.
    • 소멸자는 소멸될떄 호출되는거라 소멸될리가 없다.
    • 하나인거는 유지될테지만 메모리 영역은 수동으로 지워지지 않는다.
	void Destroy()
	{
		if (nullptr != pEngine)
			delete pEngine;
	}
  • 수동으로 지우는 Destroy함수를 만들었다.
    • 까먹지 말고 호출하자.
	// 1
    CEngine* pEngine = CEngine::GetEngine();
	// 2
    pEngine = CEngine::GetEngine();
    pEngine = CEngine::GetEngine();
    pEngine = CEngine::GetEngine();
  • 1번이 성공을했다면 2번에 CEngine클래스에 GetEngine 함수에 접근해서 여러번 호출해도 하나의 객체를 만들수 있다.
  • 오류가 발생한다 이유는?
    • GetEngine함수가 맴버함수이기 떄문이다.
      • 맴버함수에는 this가 필수지만 this가 없다.
// main.cpp 내부
    CEntity entity;
    entity.SetName(L"Monster");
//  CEntity.h 내부
	void SetName(const wstring& _Name) { this-> m_strName = _Name; }
  • 순수 가상함수라 오류가 발생한다.
    • CEntity.h로 가서 잠깐 가상함수를 푼다.
  • entity.SetName을 호출하면 SetName 함수안에다가 호출한거에 entity의 주소가 넘어가서
    • this-> m_strName을 적은 문자열인 _Name로 변경한다.
    • 호출된 객체의 내부에 존재하는 맴버(m_strName) 를 내가 전달한 문자열(Monster)로 수정한다는것이기 떄문에 entity의 내부의 이름이 변경된다.
  • 그래서 우리가 구현한 GetEngine함수도 맴버함수다.
    • CEngine 클래스의 내부에는 정적맴버 말고는 맴버가 아직은 없다.
private:
	wstring m_Name;
    
public:

	CEngine* GetEngine()	
	{																				
		/*this->*/m_Name = L"안녕";
    }
  • 맴버로 wstring을 가지고 있었다면 this가 생략이 되어있었을것이다.
    • 호출시킨 객체의 주소가 들어왔을것이다.
  • 근데 GetEngine함수는 객체를 얻어다 와주는 함수라 이상하다.
    • 일반적인 맴버함수라면 객체가 있어야 호출할수 있다.
    • 말이 안된다.
      • 객체가있어야하는데 GetEngine함수를 호출해야 객체가 나오고 GetEngine함수는 맴버함수라 객체가 필요하고 무한루프로 이상함
	// 1
	static CEngine* GetEngine()
	{													
		if(nullptr == pEngine)
		{
			pEngine = new CEngine;
		}
		return pEngine;
	}
  • 1번처럼 맴버함수 앞에 static를 붙으면 정적 맴버 함수라 한다.
    • 선언된곳이 클래스 내부니까 완전히 저 클래스 전용 함수가 된다.
    • 지금까지의 맴버함수는 객체가 있어야 호출할수 있었지만 static를 맴버함수에 붙이면 객체 없이 호출가능.
// CEntity.h 의 CEntity클래스 내부
	static void SetName(const wstring& _Name) { m_strName = _Name; }

  • 객체의 주소를 전달해야하는데 this에 아무것도 전달되지 않는다.
    • 오류가 발생한다. 정적 맴버함수로 만들어버려서 this가 없기떄문에 m_strName가 오류 발생
    • 객체가 없이 호출되니까 객체가 없는 상태로 호출되니까 this가 없다.
      - this는 이 함수를 호출한 객체의 주소가 들어오는데 객체가 없기 떄문이다.
    • 호출시킨 객체의 주소에 들어가야하는데 객체가 없으므로 본인의 맴버를 지목하는 것은 오류가 발생한다.
    • GetEngine함수는 애초에 맴버에 접근할일이 없다.
    • GetEngine함수가 하는 역할은 내부에서 엔진이라는 객체를 만들어서 주는것이다.
    • 나를 호출시킨 맴버를 건들이지않는다.
    • static를 붙여서 정적 맴버 함수로 만들어서 this를 포기하고 클래스 전용 함수 선언을해서 호출시키게 한다.
// CEngine.h의 따로 구현한 전역함수다.
CEngine* GetEngine()
{
	new CEngine;	// 프라이빗필드에 있어서 접근 불가능.
}
  • 전역함수를 쓰면 되지 왜 정적 맴버 함수를 사용하지?

    • 클래스 내부에 private필드에 기능을 사용할려고 전역함수 같으면서도 선언되어 있는 private에 접근 할수 있어서 정적 맴버 함수를 사용한다.
  • Destroy함수도 정적 맴버 함수가 되어야 한다.

// main.cpp
	// 1
    CEngine::Destroy();
    // 2
    CEngine::GetEngine()->Destroy();
  • 객체를 지워주는 함수다.
  • 1번처럼 프로그램 종료전에 유일한 객체를 지워야하는데 Destroy함수를 그냥 호출할수없다 정적 맴버였다면 객체 없이 호출가능하다.
  • 정적 맴버함수로 안만들고 호출할려면 2번처럼 말도 안되는 복잡한 구문을 처리해야한다.
    • 너무 지저분해진다.
  • 한번 호출하면 싱글톤 패턴이니까 객체는 한개니까 Destroy함수로 한번에 지우고싶은데
    • 하나밖에 없는 객체를 얻어와서 객체를 활용해서 지워야하기때문에 정적 맴버함수로 만들자.
	static void Destroy()
	{
		if (nullptr != pEngine)
			delete pEngine;
	}
  • 일반 맴버인 wstring m_Name;에는 접근할수 없지만 static CEngine* pEngine;에는 접근이 가능하다.
    • 데이터 영역에 하나 있기 떄문이다.

정적 맴버 함수 - this를 사용하지 않는다

  1. 객체가 없어도 호출가능하다.

  2. 해당 클래스의 맴버에 접근 불가능하다.

    • 단점이기도 하다.
  3. 맴버함수의 특징인 클리스의 private에 접근 가능하다.

    • 1번과2번은 일반적인 전역함수도 마찬가지지만 3번은 정적 맴버함수랑 전역 함수랑의 차이점이다.
  4. 클래스의 정적 맴버변수에는 접근 가능

  • this가 없어도 정적 맴버 변수에는 접근 가능하다.

복습할떄 문법적인 이해가 필요하다.

  • 싱글톤을 복습할떄는 문법규칙이 복잡하게 얽혀서 하고있다.
  • 문법과 메모리에 대한 이해가 있어야 코드는 간단하지만 싱글톤패턴이 자연스럽게 받아드릴수 있도록 문법적인 이해가 필요하고 복습할떄 안보고 싱글톤의 맴버와 객체를 설계도 해보고 구문 한줄한줄 왜 프라이빗인지 이런걸 다 이해를 바탕으로 싱글톤이 될것인지 작성을 하면서 복습을 해야한다.

싱글톤 패턴의 다른 방법 (2)

class CEngine
{
private:


public:
	// 1
	static CEngine* GetEngine()
	{
    	// 2
    	//static int a = 0;
		// 3
		static CEngine engine; 
        // 4
        ++ a;
        // 5
        return &engine;
	}

// 6
private:
	CEngine();
};
  • 1번처럼 정적 맴버 함수가 되어야하는 이유는 객체가 없이도 호출할수 있어야 되기 떄문이고 전역 함수가 아니라 정적 맴버함수여야 private필드에 접근할려고

  • 2번처럼 지역변수에 static를 해버리면 a는 함수안에 있는게 아니라 데이터 영역에 있다. 함수 끝나도 a라는 변수는 살아있다.

    • 3번처럼 ++a를 하면 a가 계속 0으로 초기화됬다가 1로 되는게 아니라 초기화는 1번만 되서 호출될때마다 1증감한다.
  • 3번처럼 객체를 선언하면 엔진이라는 클래스로 객체가 하나 만들어졌는데 데이터 영역에 존재한다.

    • 최초로 생성자가 한번만 호출되고 두번 호출되도 engine은 초기화는 한번만 된다.
  • 5번처럼 주소를 리턴해버리면 데이터 영역이여도 주소는 알수 있다.

    • 전용 전역함수여도 주소를 줘서 접근할수 있다.
  • 싱글톤 패턴 완성

    • 6번으로 외부에서 객체 생성막히고 3번과 5번으로 한개의 객체가 만들어지고 접근도 할수 있어서.
    • 데이터 영역에 만들어서 Destroy함수도 만들필요없이 자동으로 프로그램 끝나면 메모리 해제가 된다.

우리가 아직 막지 못한 것 - 복사생성자

	CEngine engine(*pEngine);

  • 객체가 외부에서 만들어졌다.
  • 복사 생성자를 안막아둬서 두가지로 구현한 싱글톤 패턴 둘다 뚫린다.
    • 생성자만 막았지 복사생성자는 안막음.
class AAA
{
private:
    int m_a;
    int m_b;
};

// 메인함수 안
    AAA a;
	AAA b(a);
  • a를 입력으로 하는 복사생성자가 있을것.
// AAA 클래스 내부
public:
    AAA()
        : m_a(0)
        , m_b(0)
    {}
    // 2
    AAA(const AAA& _other)
        : m_a(_other.m_a)
        , m_b(_other.m_b)
    {}
  • 1번은 기본생성자
  • 2번은 복사생성자지만 아무것도 생성자가 없다면 기본적으로 생긴다.
    • 복사가 된것처럼 생성이 된다.
    • 구현을 안해놔도 자동으로 생긴다.
public:
    void operator = (const AAA& _other)
    {
     	m_a = _other.m_a;
        m_b = _other.m_b;
    }
  • 대입연산자도 구현을 안해놔도 자동으로 생긴다.
// 메인함수 안
    AAA b = a;
  • 대입 연산자가 생성되는게 아니라 복사 생성자가 호출된다.
    • 생성자를 이용해서 기본적으로 초기화를 하고 또 대입 연산자를 하는건 2번 돌아가는것이라 그럴 빠에는 생성자의 규칙상 객체가 만들어질때 무조건 생성자를 호출되어야하는데 입력으로 들어온놈이 나랑 똑같은 타입으로 들어왔을댸 그 녀석으로 초기화되는 생성자가 있다면 기본 생성자 호출하고 대입 연산자호출하는 2단계로 생성자 한개로 동시에 처리할수 있다. 저렇게 작성을 하면 한번에 복사생성자를 처리하면되겠다고 본다.
	// 1
	CEngine engine(*pEngine);
	// 2
    CEngine engine = *pEngine;
  • 복사생성자를 만들어두면 아래위가 같은 소리다.
// CEngine.h의 CEngine 클래스 내부
public:
	CEngine(const CEngine& _other)
    {
    }
  • 복사 생성자가 초기화할것은 없지만 기본적으로 숨어있다.
    • 이것이 숨어있어서 1번과 2번처럼 뚫을수 있다.
private:
	CEngine(const CEngine& _other) = delete
  • delete를 사용해서 자동생성을 방지 할수 있다.
    //AAA(const AAA& _other)
    //    : m_a(_other.m_a)
    //    , m_b(_other.m_b)
    //{}
    AAA(const AAA& _other) = delete;
  • AAA클래스도 복사생성자를 delete해버리면 막을수 있다.

복사 생성자를 막지 않으면, singleton 객체를 얻어서 새로운 객체를 복사생성자를 통해서 생성시킬 수 있다.

  • 복사생성도 두번째 객체가 생기는것이라 그것도 막은것,
  • 액세스할수 없다고 먼저 떳다 delete구문을 인식하기도전에 private으로 막혀서.
	CEngine(const CEngine& _other) = delete;
  • 첫번째 구현한 싱글톤도 저렇게 private으로 막히겠지만 막아주는게 좋다.

  • 생성을 막는게 중요해서 소멸자는 걍 퍼블릭에 뒀다.

  • 첫번째는 동적할당으로 했고 두번쨰는 전역변수에 올려서 쉽게 구현을 했다.

    • 두번쨰 방법은 단점이 존재한다.
      • 게임에서 아예 필요가 없을떄는 미리 지우고싶은데 2번쨰 방법은 게임이 끝나야지만 해제가 된다.
      • 런타임중에 메모리 해제를 못한다.

강의코드

main.cpp

#include "pch.h"

#include "framework.h"
#include "GameClient.h"

#include "Resource.h"

#include "CEngine.h"
#include "CEntity.h"

class AAA
{
private:
    int m_a;
    int m_b;

public:
    void operator = (const AAA& _other)
    {
        m_a = _other.m_a;
        m_b = _other.m_b;
    }

public:
    AAA()
        : m_a(0)
        , m_b(0)
    {}

    /*AAA(const AAA& _other)
        : m_a(_other.m_a)
        , m_b(_other.m_b)
    {}*/

    AAA(const AAA& _other) = delete;        
};



// 전역 변수:
HINSTANCE   hInst;                       // 현재 인스턴스입니다.
HWND        g_hWnd;                     // 메인 윈도우 핸들

wchar_t szTitle[100];                  // 제목 표시줄 텍스트입니다.
wchar_t szWindowClass[100];            // 기본 창 클래스 이름입니다.

// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);


// SAL : 주석 언어
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    CEntity entity;

    entity.SetName(L"Monster");

    CEngine* pEngine = CEngine::GetEngine();

    pEngine = CEngine::GetEngine();
    pEngine = CEngine::GetEngine();
    pEngine = CEngine::GetEngine();

    // 복사생성자를 막지 않으면, 
    // singleton 객체를 얻어서 새로운 객체를 복사생성자를 통해서 생성시킬 수 있다.
    CEngine engine = *pEngine;       

    // 삭제된 복사생성자를 호출하려고 하는 경우
    //AAA a;
    //AAA b = a;



    // 윈도우 클래스 등록
    MyRegisterClass(hInstance);
        
    // 윈도우 생성
    hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
     
    // CreateWindow 윈도우 생성
    // 반환값은 윈도우 핸들(ID) 값,
    //  커널 오브젝트 : OS 가 관리하는 오브젝트, 직접적인 접근이 불가능하고 ID 값을 통해서 제어함
    g_hWnd = CreateWindowW(L"Window Class Key", L"My Game", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

    if (!g_hWnd)
        return FALSE;    

    // 윈도우를 화면에 보여줄지 말지 설정
    ShowWindow(g_hWnd, true);
    UpdateWindow(g_hWnd);
    
    // 리소스(단축키 테이블 핸들 얻기)
    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_GAMECLIENT));

    // 메세지 루프
    // 메세지 정보를 받을 구조체 변수
    MSG msg;    
      
	while (true)
	{
		// Peek 엿보다
		// 메세지가 있던 없던, 리턴된다.
		// 메세지가 있었으면 true, 메세지가 없었으면 false
		if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
		{
			// 꺼내온 메세지가 WM_QUIT 이면 프로그램 종료
			if (msg.message == WM_QUIT)
				break;

			// 꺼내온 메세지 처리
			if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}
		else
		{
			// 메세지가 큐에 없을 때에는 게임 코드 실행

		}
	}

    //CEngine::Destroy();

    return (int) msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex = {};

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_GAMECLIENT));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    //wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_GAMECLIENT);
    wcex.lpszClassName  = L"Window Class Key";
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}


// CallBack
// 함수의 주소를 알려줘서, 특정 상황(조건) 이 맞으면 알려준 함수가 호출되는 구조

// 각 윈도우들은 해당 윈도우에 메세지가 발생했을 때 처리를 해줄 프로시저함수를 등록해야한다.
// 메인 윈도우는 MyRegisterClass 함수 안에서 윈도우 정보를 만들때 호출함 함수의 주소를 등록해둠
// 도움말 윈도우는 다이얼로그 형태로서, DialogBox 함수를 호출할때 사용할 
// 프로시저 함수의 주소를 입력으로 넣어줬다.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_LBUTTONDOWN:
        ShowWindow(g_hWnd, false);
        break;

    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 메뉴 선택을 구문 분석합니다:
            switch (wmId)
            {
            case IDM_ABOUT:
                // 윈도우 생성 함수
                // 
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
            EndPaint(hWnd, &ps);
        }
        break;

    case WM_TIMER:
    {
        int a = 0;
    }
        break;


    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// 정보 대화 상자의 메시지 처리기입니다.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            //EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

pch.h

#pragma once


#include <Windows.h>
#include <vector>	// 동적배열
#include <list>		// 연결형 리스트
#include <map>		// 이진탐색트리
#include <string> // 문자열 전용 관리 

using std::vector;
using std::list;
using std::map;
using std::make_pair;

using std::string;
using std::wstring;

CEntity.h

#pragma once

// 정적 변수
// 1. 함수 내
// 2. 파일
// 3. 클래스
class CEntity
{
private:
	// 정적 맴버 선언
	static UINT g_NextID;	// 특정 클래스 내에 맴버로 정적변수를 선언

private:	
	const UINT	m_ID;		// 객체별 고유 ID
	wstring		m_strName;	

public:
	UINT GetID() { return m_ID; }

	void SetName(const wstring& _Name) { m_strName = _Name; }
	const wstring& GetName() { return m_strName; }

public:
	// CEntity 클래스는 추상클래스이다.
	//virtual CEntity* Clone() = 0; 

public:
	CEntity();
	virtual ~CEntity();
};

CEntity.cpp

#include "pch.h"
#include "CEntity.h"

// 구현
UINT CEntity::g_NextID = 0;


CEntity::CEntity()
	: m_ID(g_NextID++)
{
}

CEntity::~CEntity()
{	
}

CEngine.h

#pragma once

// =====================
// 디자인 패턴(설계 유형)
// =====================

// ==================================
// 싱글톤 패턴
// 클래스로 생성되는 객체를 1개로 제한
// ==================================
//class CEngine
//{
//private:
//	static CEngine* pEngine; // 정적 맴버
//
//private:
//	wstring	m_Name; // 맴버
//
//public:
//	// 객체를 1개 만들어서 반화해주는 함수를 구현
//	// 정적 맴버함수 - this 를 사용하지 않는다
//	// 1. 객체가 없어도 호출 가능
//	// 2. 맴버에 접근 불가능
//	// 3. 맴버함수의 특징, 클래스의 private 에 접근 가능
//	//                    클래스의 정적 맴버변수에는 접근 가능
//	static CEngine* GetEngine() // 정적 맴버함수
//	{
//		if (nullptr == pEngine)
//		{
//			pEngine = new CEngine;
//		}
//		
//		return pEngine;
//	}
//
//	static void Destroy()
//	{
//		if (nullptr != pEngine)
//			delete pEngine;
//	}
//
//private:
//	CEngine();
//  CEngine(const CEngine& _other) = delete;
//
//public:
//	~CEngine();
//};



class CEngine
{
private:


public:
	static CEngine* GetEngine()
	{
		static CEngine engine;	
		return &engine;
	}

private:
	CEngine();
	CEngine(const CEngine& _other) = delete;
};

CEngine.cpp

#include "pch.h"
#include "CEngine.h"

CEngine* CEngine::pEngine = nullptr;

CEngine::~CEngine()
{
	
}

1차 24.01.19
2차 24.01.22
3차 24.01.23
4차 24.01.24
5차 24.01.25

0개의 댓글