싱leton에 대해 간단히 이야기하다.

4780 단어
단일 인스턴스 모델을 구현할 때 첫 번째 고려 사항은 객체의 생성과 액세스에 공통된 포털이 하나뿐이므로 다음 두 가지 조건을 충족해야 합니다.
  • 액세스 포털은 객체와 관련이 없음
  • 구조 함수는 숨겨져야 하며 외부에서 직접 호출을 통해 새로운 대상을 만들 수 없습니다
  • 그래서 우리의 실현에는 두 가지 관건이 있다.
  • 정적 방법 getInstance를 접근 입구로 삼기
  • 구조 함수를private
  • 로 설정
    이 두 가지 원칙에 따라 우리는 먼저 제1판을 간단하게 실현했다.

    V1.0

    //Singleton.h
    class Singleton{
    public:
        static Singleton* getInstance();
    private:
        Singleton(){};
        static Singleton _instance;
    };
    
    //Singleton.cpp
    Singleton Singleton::_instace;
    Singleton* Singleton::getInstance(){
        return &_instance;
    }
    

    여기는 싱레톤을 잘 실현한 것 같아서 상술한 두 가지에 완전히 부합된다.그러나 "모든 코드가 완벽하지 않다"며 우리는 항상 결점과 문제점을 찾아낼 수 있다.예를 들면 여기,instance 대상은static로 설명되어 있으며, 프로그램이 실행되기 전에 정적 저장소에서 초기화됩니다.그런데 만약에 제 프로그램 전체에 이 대상을 전혀 쓰지 않았다면요?이럴 때 이것은 일종의 낭비다.프로그램에서 사용되었더라도, 나는 그것을 처음 사용했을 때 비로소 만들어지고 초기화되었으면 좋겠다.Effective C++ 조항 04에서 Scott Meyers는 이러한 실현 방식의 심각한 오류를 지적했다. 만약에 두 개의singleton 클래스가 있는데 그 중 하나는 다른 것을 매개 변수로 초기화한다. 즉, 하나static SingletonA SingletonA::_instance와 다른 하나는 이것을 매개 변수로 초기화static SingletonB SingletonB::_instance(SingletonA::_instance)가 존재하고 이 두 개의non-local static 대상은 두 개의 서로 다른 컴파일 단원에 있다.반면 C++는'다른 컴파일러 단원에 정의된non-local static 대상'의 초기화 상대 순서에 대해 명확한 정의를 내리지 않았다.그래서 SingletonB::instance 대상은 Singleton A::instance 대상이 초기화되기 전에 Singleton A::instance가 '반랜덤' 상태입니다. 예측할 수 없는 프로그램 행동을 초래할 수 있습니다.그래서 우리는 다음 버전이 생겼다.

    V1.1

    //Singleton.h
    class Singleton{
    public:
        static Singleton* getInstance();
    private:
        Singleton(){};
    };
    
    //Singleton.cpp
    Singleton* Singleton::getInstance(){
        static Singleton _instance;
        return &_instance;
    }
    

    또한 Scott Meyers가 Effective C++에서 제시한 방법인 non-local static을 local static으로 대체하면 초기화 순서 문제를 해결할 뿐만 아니라 lazy initialization도 실현할 수 있다.물론'모든 코드가 완벽하지 않다'는 것은 다중 스레드 시스템에서 초기화와 관련된race conditions를 감안하면 여러 스레드가 동시에 getInstance () 를 처음 호출할 때 정적 대상이 여러 번 발생한다는 것을 감안한다.Scott Meyers가 제시한 해결 방법은 프로그램의 단일 스레드 시작 단계에서 getInstance()를 수동으로 호출하는 것입니다.이것은 결코 우아한 문법이 아니다. 우리는 진정으로 대상이 필요할 때만 getInstance () 를 호출하기를 바란다.그럼 getInstance 방법에 자물쇠를 채워서 보호해야 합니다.그래서 우리는 또 다른 버전이 생겼다.

    V1.2

    //Singleton.h
    #include 
    class Singleton{
    public:
        static Singleton* getInstance();
    private:
        Singleton(){};
        static std::mutex _mutex;
    };
    
    //Singleton.cpp
    std::mutex Singleton::_mutex;
    Singleton* Singleton::getInstance(){
        std::lock_guard<:mutex> lck(_mutex);
        static Singleton _instance;
        return &_instance;
    }
    

    여기에 또 새로운non-local static 변수Singleton::를 도입하였습니다.mutex, 동쪽 벽을 허물고 서쪽 벽을 보수하는 것 같지 않아요?다행히 여기는 초기화 순서에 대한 질문이 필요 없습니다. Singleton::mutex의 초기화는 다른 정적 대상에 의존하지 않으며, lazy initialization은 여기서도 큰 문제가 되지 않아 돌볼 수가 없다.잠금 상호 배척을 통해 정적 변수가 초기화된 라인 안전 문제를 해결했지만 다른 호출 상황을 고려했다:instance는 이전 호출에서 만들어졌고 getInstance () 방법을 다시 호출할 때 자물쇠를 조작해야 합니다. 이것은 분명 불합리합니다.판단하고 있을 거예요instance가 존재하고 초기화되면 바늘을 되돌려줍니다. 잠금 동작을 하지 않습니다.그러면 초기화되었는지, OMG!그럼 아예 포인터 하나로 지목하는 걸로instance 대상입니다. 이렇게 하면 바늘이 NULL인지 여부로 대상이 만들어졌는지 판단할 수 있습니다.다음은 객체가 작성되었는지 여부를 판단하는 포인터 버전입니다.

    V2.0

    //Singleton.h
    #include 
    class Singleton{
    public:
        static Singleton* getInstance();
    private:
        Singleton(){};
        static std::mutex _mutex;
        static Singleton* _instance;
    };
    
    //Singleton.cpp
    std::mutex Singleton::_mutex;
    Singleton* Singleton::_instance = NULL;
    Singleton* Singleton::getInstance(){
        if (NULL == _instance){
            std::lock_guard<:mutex> lck(_mutex);
            _instance = new Singleton();
        }
        return _instance;
    }
    

    이렇게 하면 문제를 다 해결한 것 같다.천천히 두 개의 라인이 getInstance를 동시에 처음 호출할 때 NULL == _instance 조건이 성립된 것을 판단하여 구조 대상 조작을 실행해야 한다. 자물쇠의 존재로 인해 한 개의 라인 구조 대상만 있을 수 있지만 현재 한 라인의 구조가 끝난 후에 자물쇠가 물러난다. 그래서 두 번째 라인이 들어가 대상을 계속 재구성한다.보아하니 자물쇠를 채운 후NULL == _instance 조건에 대해 다시 한 번 판단해야 할 것 같다.잠금 전후 두 번 판단된 버전:

    V2.1

    //Singleton.h
    #include 
    class Singleton{
    public:
        static Singleton* getInstance();
    private:
        Singleton(){};
        static std::mutex _mutex;
        static Singleton* _instance;
    };
    
    //Singleton.cpp
    std::mutex Singleton::_mutex;
    Singleton* Singleton::_instance = NULL;
    Singleton* Singleton::getInstance(){
        if (NULL == _instance){
            std::lock_guard<:mutex> lck(_mutex);
            if (NULL == _instance){
                _instance = new Singleton();
            }
        }
        return _instance;
    }
    

    이런 방식을 Double-Checked Locking 기술이라고 합니다.그렇다면 이 방식은 아직 개선할 여지가 있는가?물론'모든 코드는 완벽하지 않다'. 우리 시스템의 많은 종류가singleton 모드라고 생각할 때 우리는 이 방법을 모든 종류에 복제해야 합니까?코드가 중복되는 것을 피하기 위해서 우리는 어떻게 실현합니까?상속아니면 클래스 템플릿?
    길이 아득히 멀어, 나는 위아래로 찾겠다

    좋은 웹페이지 즐겨찾기