진실은, 싱글 톤 및 상속은 함께 잘 놀지 않는다.
네, 네, 싱글 애호가와 GoF의 숭배는 "당신이 당신의 생성자 보호 할 경우 잘 ..."와 "당신이 이 getInstance
방법을 가지고이없는 말,이 모든 저 이상이어야합니다 수업에, 당신은 그것을 넣을 수 있습니다 ... ",하지만 그들은 단지 내 요점을 증명하고 있습니다. 싱글 톤은 싱글 톤 (singleton)과베이스 클래스 (base class)가되기 위해 여러 가지 농구를 뛰어 넘어야합니다.
하지만 질문에 대답하기 위해 우리는 싱글 톤 기본 클래스가 있다고 가정 해보십시오. 상속을 통해 독신을 어느 정도 시행 할 수 있습니다. 생성자는 더 이상 private 일 수없는 몇 가지 작업 중 하나를 수행합니다. 다른 Base
이 이미 있으면 예외를 throw합니다. 을 상속하는 클래스 Derived
도 있다고 가정 해보십시오.상속을 허용 할 것이므로 에서 상속받을 수도 있고 그렇지 않을 수도있는 Base
의 다른 하위 클래스가 여러 개있을 수 있다고 가정 해 보겠습니다.
하지만 문제가 있습니다. 바로 이미 실행 중이거나 곧 실행됩니다. 이미 객체를 생성하지 않고 Base::getInstance
을 호출하면 null 포인터가 생성됩니다. 우리는 싱글 톤 객체가 존재하는 곳으로 돌아가고 싶습니다 (Base
및/또는 Derived
및/또는 Other
일 수 있음). 그러나 이렇게하는 것이 어렵고 여전히 모든 규칙을 따르십시오. 왜냐하면 그렇게 할 수있는 몇 가지 방법이 있기 때문입니다. 모든 방법에는 몇 가지 단점이 있습니다.
Base
을 만들고 반환 할 수 있습니다. 나사 Derived
및 Other
. 최종 결과 : Base::getInstance()
은 항상 정확히 Base
을 반환합니다. 하위 클래스는 결코 재생되지 않습니다. Kinda가 목적을 이겼습니다. IMO.
우리는 파생 클래스에 getInstance
을 넣을 수 있으며 특히 Derived
을 원한다면 Derived::getInstance()
이라고하는 호출자가있을 수 있습니다. 이렇게하면 호출자가 구체적으로 Derived
을 요청해야한다는 사실을 알아야하기 때문에 커플 링이 크게 증가하고 결국 해당 구현과 연결됩니다.
마지막 인스턴스의 변형을 수행 할 수 있습니다. 인스턴스를 가져 오는 대신 함수에서 인스턴스를 생성합니다. (그 동안 함수의 이름을 initInstance
으로 변경해 봅시다. 특히 무엇을 얻는 지 신경 쓰지 않아야합니다. 새로운 Derived
을 생성하고 이것을 하나의 참 인스턴스로 설정한다고합니다.)
그래서 (아직 불명 어떤 oddness을 금지), 그것은
class Base {
static Base * theOneTrueInstance;
public:
static Base & getInstance() {
if (!theOneTrueInstance) initInstance();
return *theOneTrueInstance;
}
static void initInstance() { new Base; }
protected:
Base() {
if (theOneTrueInstance) throw std::logic_error("Instance already exists");
theOneTrueInstance = this;
}
virtual ~Base() { } // so random strangers can't delete me
};
Base* Base::theOneTrueInstance = 0;
class Derived : public Base {
public:
static void initInstance() {
new Derived; // Derived() calls Base(), which sets this as "the instance"
}
protected:
Derived() { } // so we can't be instantiated by outsiders
~Derived() { } // so random strangers can't delete me
};
그리고 당신의 초기화 코드에서
, 당신은 당신이 입력에 따라
Base::initInstance();
또는
Derived::initInstance();
을 말 ... 같이 좀 밖으로 작동 싱글 톤을 원한다. 물론
Derived
특정 함수를 사용하기 위해서는
Base::getInstance()
에서 반환 값을 캐스팅해야하지만 으로 재정의 된 가상 함수를 포함하여
Base
에 정의 된 함수를 모두 사용할 수 있습니다. 그 일을이 방법도하지만, 자신의 단점들을 가지고
참고 :
그것은 기본 클래스에 독신을 강요하는 부담의 대부분을 넣습니다. 기본에 이와 유사한 기능이없고 변경할 수없는 경우 다소 괴롭습니다. 각 클래스는 보호 소멸자를 선언 할 필요가, 또는 누군가가 따라 올 수 적절하게 (에) 캐스팅 후 하나 개의 인스턴스를 삭제 -
기본 클래스는하지만, 책임의 모든을받을 수 없어 모든 것이 지옥에갑니다. 더 나쁜 것은 컴파일러가이를 적용 할 수 없다는 것입니다.
우리가 보호하는 데스트 라크 터를 사용하여 인스턴스를 삭제하는 것을 방지하기 때문에 컴파일러가 더 두렵다면, 런타임도 프로그램이 종료 될 때 인스턴스를 제대로 삭제할 수 없습니다. 안녕히 가세요, RAII ... 안녕하세요. "메모리 누수가 감지되었습니다"라는 경고입니다. 물론 메모리는 궁극적으로 적절한 OS에 의해 재 확보 될 것입니다.그러나 소멸자가 실행되지 않으면, 당신은 당신을 위해 정리 작업을 할 수 없습니다. 종료하기 전에 일종의 정리 기능을 호출해야합니다. 그러면 RAII가 제공 할 수있는 보장과 가까운 곳에서 당신을 포기할 수 없습니다.)
IMO가 수행하는 initInstance
메소드가 표시됩니다 모든 사람이 볼 수있는 API에 실제로 속합니다. 원하는 경우 initInstance
을 비공개로 만들고 init 함수를 friend
으로 만들 수 있습니다. 그런 다음 클래스가 코드 외부에 대해 가정을하고 커플 링 문제가 다시 발생합니다.
위의 코드는 스레드로부터 안전하지 않습니다. 필요한 경우 스스로 해결할 수 있습니다.
진지하게, 덜 고통스러운 경로는 독신을 시행하려는 것을 잊어 버리는 것입니다. 인스턴스가 하나만 존재한다는 것을 확인하는 가장 복잡한 방법은 인 경우에만을 생성하는 것입니다. 여러 곳에서 사용해야하는 경우 종속성 삽입을 고려하십시오. (비 - 프레임 워크 버전은 "필요한 것을 물건에 물건을 넘기는"금액입니다. : P) 나는 싱글 톤과 상속에 대해 스스로 나 자신을 증명하려고 시도하고 위의 물건을 디자인하고 단지 나 자신에게 재앙을 재확인했습니다. 그 조합은 나는 실제 코드에서 실제로 그렇게하는 것을 추천하지 않는다.
대부분의 경우, 클래스에 싱글 톤 기본 클래스가있는 경우에는 완전히 쓸모가 없습니다. 파생 클래스 인스턴스는 기본 클래스의 인스턴스이기 때문에 절대 만들 수 없습니다. 당신은 그 중 하나를 만들 수 없습니다. (나는 "합리적인"예외를 상상할 수 있지만 기본 클래스가 마음 속에 파생 된 아이디어로 특별히 설계된 경우에만 해당됩니다.) – Hurkyl