본문 바로가기
프로그래밍/C++

C++ ] 싱글톤(Singleton) 클래스

by eteo 2025. 12. 15.
반응형

 

 

 

싱글톤(Singleton) 패턴

싱글톤(Singleton) 패턴은 "프로그램 전체에서 오직 하나의 인스턴스만 존재해야 하는 클래스"를 만들 때 사용하는 디자인 패턴이다. 주로 전역 상태를 관리하는 클래스나 설정 파일, 시스템 로그 등의 공용 리소스를 다루는 클래스에서 사용한다.

 

 

 

 

싱글톤(Singleton) 클래스 구현 방법

 

싱글톤 클래스를 구현할 때는 다음 요소를 갖춰서 구현해야 한다.

 

  1. 생성자 비공개 : 외부에서 new 연산자를 사용해서 인스턴스를 직접 생성하는 것을 막기 위해 생성자는 private 또는 protected로 선언해야 한다.
  2. 복사 및 이동 방지 : 단일 인스턴스를 유지 하기 위해 복사 생성자, 복사 대입 연산자, 이동 생성자, 이동 대입 연산자를 명시적으로 삭제해야 한다.
  3. 정적 인스턴스와 정적 접근 메서드 : 싱글톤 인스턴스는 클래스 내부에 static 지역 변수(C++11 이상)로 선언하고, 이를 반환해주는 정적 메서드를 제공한다.

 

class Singleton {
// 생성자 비공개
private:
    Singleton() { /* 초기화 코드 */ }
    ~Singleton() = default;

    // 복사 및 이동 방지
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;

public:
    // 정적 접근 메서드
    static Singleton& getInstance() {
        // 정적 인스턴스 (로컬 정적 변수는 C++11부터 thread-safe하게 초기화됨이 보장됨)
        static Singleton instance;
        return instance;
    }

    // 싱글톤 클래스의 기능 메서드
    void someMethod() {
        // ...
    }
};

// 사용 예시
int main(void) {
    Singleton::getInstance().someMethod();
    return 0;
}

 

 

위 구현 방식의 핵심은, 외부에서 인스턴스를 임의로 생성하거나 접근할 수 없고, 오직 getInstance() 메서드를 통해 동일한 인스턴스를 획득해 사용한다는 점이다.

 

 

 

 

싱글톤 인스턴스 생성 및 소멸 흐름

 

1. getInstance()가 처음 호출되는 순간, 내부의 정적 지역 변수가 초기화되며 이 객체가 싱글톤 인스턴스로 동작한다.

 

참고로, C 언어에서 함수 내부의 static 지역 변수는 프로그램 시작 시점에 초기화되지만, C++에서는 함수가 최초로 호출될 때 초기화된다. 이러한 특성을 Lazy Initialization(지연 초기화)라고 한다.

 

2. 이후 getInstance()를 여러 번 호출해도 항상 동일한 인스턴스가 반환된다.

 

3. 프로그램 종료 시점에는 정적 객체 소멸 규칙에 따라 자동으로 해제된다.

 

한편, C++11부터는 static 지역 변수 초기화가 원자적으로 처리되므로 멀티스레드 환경에서도 안전하게 단일 인스턴스를 생성할 수 있다.

 

 

 

 

싱글톤 인스턴스 사용 예시

 

다음은 싱글톤 패턴을 사용한 로거 클래스 예시이다.

 

#include <iostream>
#include <fstream>
#include <string>
#include <ctime>
#include <iomanip>
#include <chrono>
#include <mutex>
#include <thread>
#include <vector>

class Logger {
private:
    std::ofstream logFile;
    std::mutex logMutex;

    Logger() {
        logFile.open("log.txt", std::ios::out | std::ios::app);
        if (!logFile.is_open()) {
            std::cerr << "Warning: Could not open log file!" << std::endl;
        }
    }

    ~Logger() {
        if (logFile.is_open()) {
            logFile.close();
        }
    }

    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
    Logger(Logger&&) = delete;
    Logger& operator=(Logger&&) = delete;

    std::string getCurrentTime() {
        auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
        std::tm tm_buf;

        localtime_s(&tm_buf, &now);	// Windows/MSVC, use localtime_r for POSIX
        char buffer[80];
        std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm_buf);
        return std::string(buffer);
    }

public:
    static Logger& getInstance() {
        static Logger instance;
        return instance;
    }

    void log(const std::string& message) {
        std::lock_guard<std::mutex> lock(logMutex);

        std::string timeStr = getCurrentTime();
        std::cout << "[LOG] " << timeStr << " - " << message << std::endl;
        if (logFile.is_open()) {
            logFile << "[FILE] " << timeStr << " - " << message << std::endl;
            logFile.flush();
        }
    }
};

#define LOG(message) Logger::getInstance().log(message)

void worker_thread(int id) {
    LOG("Worker Thread " + std::to_string(id) + " started.");
    std::this_thread::sleep_for(std::chrono::milliseconds(50));
    LOG("Worker Thread " + std::to_string(id) + " finished work.");
}

int main() {
    LOG("Application started successfully.");

    std::vector<std::thread> threads;
    const int num_threads = 8;

    for (int i = 0; i < num_threads; ++i) {
        threads.emplace_back(worker_thread, i + 1);
    }

    for (auto& t : threads) {
        t.join();
    }

    LOG("Application terminated.");

    return 0;
}

 

 

반응형