Game Develop

[C++] 싱글톤(Singleton)에 대하여 본문

C++/C++

[C++] 싱글톤(Singleton)에 대하여

MaxLevel 2020. 10. 18. 23:37

- 싱글톤이란

                                                                                                                                                      

 

싱글톤(Singleton)은 싱글 인스턴스(Single Instance)의 줄임말로, 말 그대로 프로그램 내에서 특정 클래스의 인스턴스를 하나만 생성한다음 공유하여 사용하는 디자인패턴이다.

보통 여러곳에서 접근해야하는 유틸성 성격을 가진 클래스를 싱글톤으로 생성하곤한다.( 사운드매니저,타이머클래스 등등)

예를들어 게임개발시 필수기능인 Timer클래스는 실시간으로 시간이랑 FPS를 계산하는 클래스이기 때문에 실행 초기에 인스턴스를 하나만 생성해서 계속 업데이트시키면 된다.

하지만 코딩을 하다보면 여러곳에서 현재시간을 이용하여 구현해야하는 기능들이 많기 때문에 이 인스턴스에대한 접근을 전역적으로 하게 해야한다. 이 때 적합한게 싱글톤패턴이다.

싱글톤패턴에 대한 구현은 정말 다양하기때문에 밑의 예제가 전부가 아니라는것을 미리 당부하고싶다.

밑의 예제는 그저 그 중 하나일 뿐이다.

 

싱글톤은 디자인패턴을 잘 모르는 사람이라도 한번쯤은 들어봤을법한 패턴이기도 하고 실제로 사용하기 때문에 간단한 예제코드를 보면서 조금씩 알아보자.

 

 

 

- 예제 코드

                                                                                                                                                      



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Singleton
{
private::
    static Singleton* instance;
 
public:
    static Singleton* Get()
    {
        if (instance == nullptr)
            instance = new Singleton();
 
        return instance;
    }

void TestFunction();

    static void Delete()
    {
        delete instance;
    }
};
 
cs

    

스태틱변수는 컴파일 시에 메모리에 할당된다. 프로그램 실행 시점으로 보자면 거의 맨 초기에 할당된다고 보면되고 따로 해제하지 않는 이상 프로그램 종료시까지 메모리가 유지된다. (즉, 스태틱변수의 라이프싸이클은 메모리할당 이후 프로그램 종료시까지이다.)

 

이 함수에서 주의할 점은 instance변수를 클래스밖에서 초기화를 해줘야한다.

나같은 경우 cpp파일에서 인클루드 아랫부분에서 바로 초기화를 해준다.

  Singleton* Singleton::instance = nullptr;  

 

함수를 간단히 설명해보자면 원하는곳에서 Singleton::Get()->TestFunction() 식으로 호출해서 사용하면 된다.

스태틱변수와 마찬가지로 스태틱함수이기 때문에 전역함수처럼 다른곳에서 원하는대로 호출이 가능하다. 

처음에는 nullptr로 초기화 되어있기 때문에 nullptr일 시 인스턴스를 할당받고 그 이후로는 할당받았던 인스턴스를 가져다 쓰기만 하면 된다.

보통 싱글턴을 쓰는 클래스들은 프로그램 종료시까지 사용하기때문에 굳이 Delete함수를 만들필요가 없지만 어떤 구간 이후부터는 필요가 없다던가(필요없는데 굳이 메모리에 적재하고있을 이유는 없으므로) 혹은 해제타이밍을 구조적으로 명확히 하고 싶을때도 있기 때문에 Delete함수도 같이 쓰기도 한다.(같은 이유로 Create 함수를 따로 만들기도 한다.)

 

- 상속받아서 싱글톤화 만들기.

                                                                                                                                                      

 

 그냥 위의 예제처럼 싱글톤화하고 싶은 클래스에 직접적으로 코딩해도 되지만, 싱글톤클래스를 따로 생성하고 상속받게 함으로써 편리하게 싱글톤화하게 만들수있다. 미리 알아채신분도 계시겠지만 템플릿을 활용하여 만드는 방법이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
template <typename T>
 
class Singleton
{
protected:
    static T* instance;
 
public:
    static T* Get()
    {
        if (instance == nullptr)
            instance = new T();
 
        return instance;
    }
 
    static void Delete()
    {
        delete instance;
    }
};
 
template<typename T>
T* Singleton<T>::instance = nullptr;
cs

 

처음의 예제코드랑 다른점은 그저 자료형이 템플릿인것과 초기화를 클래스 밑에서 해주는것뿐이다.

(따로 cpp구현부가 없기때문에 헤더에서 초기화를 했다.)

이제 이 Singleton클래스를 상속받는 클래스는 Singleton클래스가 된다.

아래처럼 사용하면 된다. 예제 클래스로는 Timer클래스를 가져왔다.

 

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
#pragma once
 
class Timer : public Singleton<Timer>
{
private:
    friend class Singleton;
 
    float timeScale;
    float timeElapsed;
 
    __int64 curTime;
    __int64 lastTime;
    __int64 periodFrequency;
 
    int frameRate;
    int frameCount;
 
    float oneSecCount;
    float runTime;
 
    float lockFPS;
 
    Timer();
    ~Timer();
 
public:
 
    void Update();
 
    void SetLockFPS(float value) { lockFPS = value; }
 
    int GetFPS() { return frameRate; }
    float GetElapsedTime() { return timeElapsed; }
    float GetRunTime() { return runTime; }
};
cs

 

위와같이 상속받아서 사용하면 된다.

단 여기서 알아두어야 할점이 몇가지 있다.

일단 Timer클래스에서 Singleton을 friend로 지정한게 보일것이다.

friend는 강제로 접근을 하게 해주는 키워드인데 이 friend를 사용한 이유는, Timer 클래스의 생성자,소멸자를 private에 둘 경우 Singleton 클래스에서 접근을 하지 못한다. 보통 Singleton클래스는 다른곳에서 함부로 호출하지못하도록 생성자와 소멸자를 private에 선언하는데 위와 같이 상속받아서 사용하려니 Singleton클래스에서 접근을 못해버리는 상황이 발생한다. 그래서 friend를 사용하여 Singleton에서 자식클래스인 Timer의 생성자에 접근하게 만들어주는 것이다.

물론 무분별한 friend의 남발은 객체지향에서의 클래스의존도가 높아지기 때문에 (결합도 증가) 자제하도록 하자.