ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 게임 프로그래밍 패턴 Part 2 디자인 패턴 다시 보기 - 싱글턴
    보관함 2020. 3. 1. 14:51

    게임 프로그래밍 패턴로버트 나이스트롬 (Robert Nystrom)
    상세보기

    이 시리즈는 [게임 프로그래밍 패턴]에 등장하는 팁을 정리하고 패턴을 직접 구현하거나 구현되어 있는 패턴을 확인하는 것으로 해당 패턴에 대해 이해하는 것을 목표로 한다.

     

    이번 포스팅에서는 Part 2의 다섯 번째 패턴인 싱글턴을 살펴보고 저자가 주장하는 싱글턴 사용을 피해야 하는 이유에 대해서 자세히 살펴보는 것을 목표로 한다.


    싱글턴 패턴이란?)

    [GoF디자인 패턴]에서는 싱글턴 패턴을 다음과 같이 소개했다.

    오직 한 개의 클래스 인스턴스만을 갖도록 보장하고, 이에 대한 전역적인 접근점을 제공합니다.

     

    코드로 살펴보면 다음과 같이 인스턴스 생성이 하나만 되도록 제한하고 전역 변수로 접근하도록 만드는 것이다.

    class TestClass
    {
    public :
        static FileSystem& instance()
        {
            static FileSystem *instance = new FileSystem();
            return *instance;
        }
    
    private:
        FileSystem() {}
    }

     

    이를 통해 클래스를 include 하기만 하면 어디에서든 이 클래스에 접근하여 기능을 이용할 수 있다.


    아마 위 코드와는 조금 다른 형태로 알고 있는 사람도 많겠지만 저자는 최신 C++ 컴파일러의 경우 위 방법으로 생성 할때 기존 방법과는 달리 스레드 안전하다고 한다.


    싱글턴 패턴의 문제점)

    디자인 패턴의 소개를 다시 살펴보면 이상함을 느낄 수 있다.

    오직 한 개의 클래스 인스턴스만을 갖도록 보장하고, 이에 대한 전역적인 접근점을 제공합니다.

     

    바로 '하고'라는 수식어로 두 문장이 어어져있다는 점이다. 저자의 표현을 빌리면 싱글턴의 문제점은 다음과 같다.

     

    싱글턴은 문제가 하나뿐일 때도 두 가지 문제를 풀려 든다

     

    이 외에도 싱글턴은 기본적으로 전역 변수이기 때문에 다음과 같은 전역 변수가 가지는 문제점을 그대로 가지고 있다.

     

        1. 전역 변수는 코드를 이해하기 어렵게 한다.

             : 전역 변수에서 문제가 발생한 경우 전역 변수를 사용하는 모든 코드를 살펴봐야할 필요가 있다.

        2. 전역 변수는 커플링을 조장한다.

             : 전역 변수가 아니라면 다른 방법을 사용할 것이지만 전역 변수라면 그것을 그대로 사용하고자 한다(사용 방법 제한이 어렵다).

        3. 전역 변수는 멀티 스레딩 같은 동시성 프로그래밍에 알맞지 않다.

             : 모든 스레드가 동시에 수정할 수 있는 공간이 생기는 것이므로 이것이 어려운 스레드 버그를 만들어낼 수 있다.

    싱글턴의 대안)

    먼저, 인스턴스를 하나만 가지도록 보장하는 방법은 어디에서나 생성할 수 있도록 허용하되 어려개를 생성하려고 시도한 경우 단언문(Assert 문)에 걸리도록 만드는 것이다.

     

    코드를 살펴보면 다음과 같다.

    class TestClass
    {
    public:
        TestClass()
        {
            assert(!inst);
            inst = true;
        }
        
        ~TestClass()
        {
            inst = false;
        }
        
    private:
        static bool inst;
    }
    
    TestClass::inst = false;

     

    이 코드의 문제점은 런타임에 인스턴스 개수를 확인하기 때문에 싱글턴처럼 컴파일 타임에 인스턴스 개수가 하나만 생성되도록 보장하지 못한다는 점이다.

     

    인스턴스에 쉽게 접근하는 방법은 크게 세가지가 있다.

    • 넘겨주기
    • 상위 클래스로부터 얻기
    • 이미 전역인 객체로부터 얻기

    이 중 '넘겨주기'는 기존 처럼 하수를 통해 인스턴스를 넘겨주는 방법을 말하고 '이미 전역인 객체로부터 얻기'는 말 그대로 전역인 객체를 모두 없앨 수 없으니 이미 전역인 객체가 인스턴스를 가지도록 만드는 것이다.

     

    상위 클래스로부터 얻기는 다음과 같이 어떤 오브젝트의 상위 클래스에 특정 인스턴스를 가지도록 하고 그것을 상속하는 클래스만 해당 인스턴스에 접근할 수 있도록하여 접근 제한을 만드는 것이다.

    class GameObject
    {
    protected:
        Log& getLog() { return log; }
    
    private:
        static Log& log;
    }
    
    class Enemy : public GameObject
    {
        void doSomething()
        {
            getLog().write("Some Log");
        }
    }

    댓글

Designed by Tistory.