싱글톤 패턴의 핵심은 클래스의 인스턴스가 단 하나만 생성되도록 보장하고, 그 인스턴스에 대한 전역적인 접근점을 제공하는 패턴이다
프로그램 전체에서 유일하게 존재해야 하며, 여러 곳에서 공유하고 참조해야 하는 자원(설정, 데이터베이스 연결, 로그 기록 등)을 관리할 때 싱글턴 패턴이 아주 유용하다
오직 클래스 자신만이 생성자를 호출할 수 있게 한다.
static 멤버는 클래스의 모든 객체가 공유하는 멤버이다. 프로그램이 시작될 때 메모리의 특정 공간(데이터 영역)에 딱 한번만 할당된다. 이 성질을 이용해 유일한 인스턴스를 저장하는 변수를 만든다.
생성자가 private 이므로 외부에서 객체를 얻을 방법이 없다. 따라서 유일한 인스턴스를 외부로 반환해 줄 함수가 필요하다.
#include <iostream> using namespace std; class Airplane { private: static Airplane* instance; // 유일한 비행기 객체를 가리킬 정적 포인터 int positionX; // 비행기의 X 위치 int positionY; // 비행기의 Y 위치 // private 생성자: 외부에서 객체 생성 금지 Airplane() : positionX(0), positionY(0) { cout << "Airplane Created at (" << positionX << ", " << positionY << ")" << endl; } public: // 복사 생성자와 대입 연산자를 삭제하여 복사 방지 Airplane(const Airplane&) = delete; Airplane& operator=(const Airplane&) = delete; // 정적 메서드: 유일한 비행기 인스턴스를 반환 static Airplane* getInstance() { // 인스턴스가 아직 없으면 새로 생성 if (instance == nullptr) { instance = new Airplane(); } // 이미 존재하면 기존 인스턴스를 반환 return instance; } // 비행기 위치 이동 void move(int deltaX, int deltaY) { positionX += deltaX; positionY += deltaY; cout << "Airplane moved to (" << positionX << ", " << positionY << ")" << endl; } // 현재 위치 출력 void getPosition() const { cout << "Airplane Position: (" << positionX << ", " << positionY << ")" << endl; } }; // Static member // // static 키워드를 사용해 정의된 정적 멤버 변수는 클래스의 모든 인스턴스에서 공유된다 // 클래스의 모든 객체가 같은 메모리 위치를 참조 // 정적 멤버 변수는 클래스 내부에서 선언되지만, 클래스 외부에서 정의하고 초기화 할 수 있다. Airplane* Airplane::instance = nullptr; int main() { // 유일한 비행기 인스턴스를 가져옴 Airplane* airplane = Airplane::getInstance(); airplane->move(10, 20); // 비행기 이동 airplane->getPosition(); // 또 다른 요청도 같은 인스턴스를 반환 Airplane* sameAirplane = Airplane::getInstance(); sameAirplane->move(-5, 10); // 비행기 이동 sameAirplane->getPosition(); return 0; }
- 비공개(private) 생성자 : Airplane() 생성자를 private으로 선언하여 클래스 외부에서 new Airplane()을 통해 객체를 생성하는 것을 원천적으로 차단한다.
- 정적(static) 멤버 변수 : static Airplane* instance는 클래스 자체에 속하는 변수로, Airplane 객체가 몇 개가 있든(여기서는 하나겠지만) 이 변수는 단 하나만 존재, 이 변수가 유일한 인스턴스를 저장하는 역할을 한다.
- 정적(static) 접근 메서드 : static Airplane* getInstance()는 유일한 인스턴스를 얻을 수 있는 유일한 창구이다. 이 메서드는 instance가 nullptr일 때(즉, 아직 아무도 인스턴스를 요청한 적이 없을 때) 단 한 번만 생성자를 호출하여 객체를 생성, 그 이후의 모든 getInstance() 호출은 이미 만들어진 동일한 객체의 주소를 반환한다.
복사 생성자 ,대입 연산자 삭제를 안할시
#include <iostream> using namespace std; class Airplane { private: static Airplane* instance; // 유일한 비행기 객체를 가리킬 정적 포인터 int positionX; // 비행기의 X 위치 int positionY; // 비행기의 Y 위치 // private 생성자: 외부에서 객체 생성 금지 Airplane() : positionX(0), positionY(0) { cout << "Created at (" << positionX << ", " << positionY << ")" << endl; } public: // 복사 생성자와 대입 연산자를 삭제하여 복사 방지 // Airplane(const Airplane&) = delete; // Airplane& operator=(const Airplane&) = delete; // 정적 메서드: 유일한 비행기 인스턴스를 반환 static Airplane* getInstance() { if (instance == nullptr) { instance = new Airplane(); } return instance; } // 비행기 위치 이동 void move(int deltaX, int deltaY) { positionX += deltaX; positionY += deltaY; cout << "moved to (" << positionX << ", " << positionY << ")" << endl; } // 현재 위치 출력 void getPosition() const { cout << "Position: (" << positionX << ", " << positionY << ")" << endl; } }; // Static member // // static 키워드를 사용해 정의된 정적 멤버 변수는 클래스의 모든 인스턴스에서 공유된다 // 클래스의 모든 객체가 같은 메모리 위치를 참조 // 정적 멤버 변수는 클래스 내부에서 선언되지만, 클래스 외부에서 정의하고 초기화 할 수 있다. Airplane* Airplane::instance = nullptr; int main() { Airplane* airplane = Airplane::getInstance(); airplane->move(10, 20); airplane->getPosition(); // 복사 생성자 Airplane newairplane = *airplane; // 대입 연산자 Airplane newairplane2 = *Airplane::getInstance(); // 새로운 객체 생성 newairplane.move(20, 30); newairplane2.move(50, 100); newairplane.getPosition(); newairplane2.getPosition(); airplane->getPosition(); // 원본 싱글턴 객체의 위치는 변하지 않음 return 0; }
airplane,newairplane,newairplane2는 모두 다른 메모리 주소를 가진다.
newairplane,newairplane2에서 move값을 수정하더라도 airplane에는 값이 바뀌지않고 단 하나의 객체라는 싱글톤의 규칙이 깨진다.
C++11에서 도입된 기능 특정 함수를 명시적으로 삭제하는 것을 의미
함수 끝에 = delete; 컴파일러에게 이 함수를 사용할 수 없게 만들어라 라고 명령하는 것
만약 누군가 삭제된 함수를 호출하려고 하면, 컴파일 에러를 발생시킨다.
#include <iostream> using namespace std; class SettingsManager { public: // 1. 반환 타입을 참조(&)로 변경 static SettingsManager& getInstance() { // 2. 함수 내부에 static 객체를 선언 (C++11부터 Thread-safe) static SettingsManager instance; return instance; } // 복사 및 대입 방지 SettingsManager(const SettingsManager&) = delete; void operator=(const SettingsManager&) = delete; void setVolume(int vol) { volume = vol; } int getVolume() const { return volume; } private: // 생성자를 private SettingsManager() : volume(70) { cout << "SettingsManager 인스턴스 생성됨" << endl; } int volume; }; int main() { // 이렇게 사용하도록 강력하게 유도됨 SettingsManager::getInstance().setVolume(95); cout << "현재 볼륨: " << SettingsManager::getInstance().getVolume() << endl; // 물론, 여전히 변수에 담을 수는 있습니다. SettingsManager& settings_ref = SettingsManager::getInstance(); settings_ref.setVolume(100); cout << "참조를 통한 볼륨: " << SettingsManager::getInstance().getVolume() << endl; return 0; }
함수 지역 정적 변수 (Function-Local Static Variable)
static SettingsManager instance; 코드가 getInstance() 함수 내부에 있다.
지연 초기화 (Lazy Initialization) : 이 instance 객체는 프로그램 시작 시가 아니라, getInstance() 함수가 처음으로 호출되는 시점에 단 한 번만 생성되고 초기화된다.
자동 소멸 : new를 사용하지 않았기 때문에 delete를 호출할 필요가 없다.
스레드 안전성 (Thread-Safety) : C++11 표준부터, 함수 지역 정적 변수의 초기화는 스레드에 안전하도록 보장된다. 즉, 여러 스레드에서 동시에 getInstance()를 호출하더라도 instance가 여러 번 생성되는 문제 없이 정확히 단 한 번만 초기화참조(Reference) 반환
getInstance()가 포인터(*) 대신 참조(&)를 반환
안전성 : 사용자가 실수로 delete SettingsManager::getInstance() 같은 코드를 호출하여 객체를 파괴하는 것을 막을 수 있다.
편의성 : 포인터 연산자 -> 대신 일반 객체처럼 . 연산자를 사용하여 멤버 함수에 접근할 수 있어 코드가 더 직관적이고 간결하다.