본문 바로가기

UE5/UE/C++ 스터디

Game Programming Patterns - Singleton Patterns

스마일게이트 퓨처랩에서는 게임 개발자들에게 실질적인 도움이 되는 다양한 행사를 꾸준히 진행하고 있다.

안그래도 개인적으로 C++과 언리얼을, 조금씩, 천천히 공부하고 있었는데 마침 이런 상황에 찰떡인 프로그램이 있었다.

 

게임사중 게임 생태계에 대한 사회적 환원에 있어 최고를 자랑하는

아니 그 자체를 위해 설립된 법인이라고도 할 수 있는 퓨처랩의 '인디게임 개발 장학팀 2기'라는 프로그램이다.

 

이 프로그램에 선정되고자, 머리속의 구상으로만 머물러있던 (1인)스터디 계획의 일정과 커리큘럼을 구체화했다.

또한, 1인 참여 후 팀 빌딩을 통해 스터디원을 확보하려는 한편 혹여 같이 할 사람이 있을까 알아보다 후배들을 합류시켰다.

본 카테고리는, 원래도 업로드할 예정이긴 했지만, 이 스터디(선정 유무와 관계 없이 진행)에서도 C++의 이론적인 부분을 공부한 내용을 기록할 것이다. 교재는 Game Programming patterns.

 

Game Programming Patterns - Singleton Patterns

싱글턴 패턴은 그 의도와는 달리, 장점보단 단점이 더 많다.
-> 면접보러 갈 때 마다 기억 안나면 싱글턴 들이밀며 넘어갔는데, 이래서 떨어진듯.

 

싱글턴의 특징

1) 클래스 내에 오직 단 한개의 instance만 갖도록 보장
2) 전역 접근 가능

// C++의 싱글턴 - 포인터 사용


class FileSystem
{
public:
	static FileSystem* instance()
	{
		if (instance_ == NULL) instance_ = new FileSystem();
		return *instance_;
	}
    
private:
	FileSystem() {}
	~FileSystem() {}
	static FileSystem* instance_;
};


// ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// C++의 싱글턴 - 참조자 사용


class FileSystem
{
public:
	static FileSystem& instance()
	{
    	static FileSystem instance_;
		return instance_;
	}
    
private:
	FileSystem() {}
	~FileSystem() {}

};


// ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
// C#의 싱글턴


public class FileSystem
{
	private static FileSystem instance = null;
    
    public FileSystem GetInstance()
    {
		if(instance == null)
        	instance = this;
        
        return instnace;
    }
}

 

*Lazy initialize(지연 초기화) : 실제 인스턴스가 사용되는 시점에 인스턴스 초기화
-> 장점 : 필요할 때 인스터스를 생성해 메모리 누수 방지
-> 단점 : 멀티 스레드 환경에서 동시에 호출될 경우 인스턴스가 다중 생성될 여지 존재

*Eager initialize(이른 초기화) : 싱글턴 클래스가 생성될 때 인스턴스 초기화
-> 장점 : Thread-safe하게 인스턴스 생성
-> 단점 : 생성된 인스턴스가 계속 메모리 차지

 

C++에서 전역 객체(static, extern)는 프로그램이 실행될 때 초기화 된다.

-> static : 파일 단위 전역 변수

-> extern : 프로젝트 단위 전역 변수

 

전역 객체는 다음과 같이 5개의 유형으로 구분됨

 

(1) 전역객체
(2) namespace 유효범위에서 정의된 객체
(3) 파일 유효범위에서 static으로 정의된 객체
(4) 클래스 안 static 객체
(5) 함수 안 static 객체.

 

이중 4, 5번은 지역 전역 객체, 나머지는 비지역 전역 객체로 구분함.

-> 지역 전역 객체끼리만 선언 순서에 의한 초기화 순서가 보장됨.

 

즉 초기화 순서를 알 수 없기에 이른 초기화로 싱글턴을 사용할 경우 초기화되지 않은 전역 변수에 접근할 가능성이 있어 지연 초기화로 싱글턴을 사용하는 것을 권장함.

*C#의 static

1) 클래스 멤버로서의 static 키워드만 허용되고, 해당 타입(클래스, 변수, 메서드)에 접근할 때 초기화됨.

2) 전역 변수 대신 (전역) 필드라는 단어를 사용함.

 

// Effecttive C++에서 추천하는 싱글턴 구현 방식
class Singleton
{
};

Singleton* GetInstance()
{
    static Singleton instance;
    return &instance;
}

 

C++에서는 함수 호출을 통한 전역 객체의 초기화를 허용한다.

또한, 전역 객체의 초기화를 위해 호출된 함수는 main 이전에 호출된다.

즉, 아래와 같은 코드의 경우  main보다 Init이 먼저 실행됨.

TestClass Init()
{
	...       
	return inst;
}
    
static TestClass instance = Init();

int main()
{
	...
}

 

항상 그렇지만 단순하게 기본 문법책에서 소개하는 여러 명제들 뒤에는 꽤 깊은 cs가 숨어있다.

이 포스팅에서 소개한, 전역 변수의 초기화 순서를 보장하는지에 대한 여부, 정확히는 그 상세한 이유에 대해 찾는데 1주일이나 걸렸다.

 

해결하는 것은 의외로 쉬웠는데, C# 관련된 저서를 작성하신 분의 홈페이지에 질의응답을 통해서였다.

 

도움을 받은 만큼, 그 분의 홈페이지를 소개한다.

 

https://www.sysnet.pe.kr/0/0

 

J & J - 정성태의 닷넷 이야기: Digital Stories

 

www.sysnet.pe.kr