기술나눔

C에서 싱글톤 패턴의 설계 및 구현: 기초부터 고급까지

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

C++의 싱글톤 패턴 설계 및 구현: 기초부터 고급까지

제목: C++의 싱글톤 패턴에 대한 심층적인 이해: 설계, 구현 및 적용

소프트웨어 개발에서 디자인 패턴은 특정 문제에 대한 입증된 솔루션으로 개발자가 보다 일관되고 재사용 가능하며 유지 관리 가능한 방식으로 소프트웨어를 구축하는 데 도움이 됩니다. 싱글톤 패턴은 가장 기본적이고 널리 사용되는 디자인 패턴 중 하나입니다. 이 패턴은 클래스에 인스턴스가 하나만 있도록 하고 이 인스턴스를 얻기 위한 전역 액세스 지점을 제공합니다.이 기사에서는 기본 구현부터 스레드 안전 버전, 스마트 포인터 및std::call_once) 응용 프로그램을 작성하고 독자들에게 포괄적이고 실용적인 가이드를 제공하기 위해 노력합니다.

1. 싱글톤 패턴의 기본 개념

싱글톤 패턴의 핵심 아이디어는 클래스에 인스턴스가 하나만 있도록 하고 전역 액세스 지점을 제공하는 것입니다. 이 모드는 리소스(예: 구성 파일 판독기, 로거, 데이터베이스 연결 풀 등)에 대한 액세스를 제어해야 할 때 유용합니다. 싱글톤 패턴의 핵심은 다음과 같습니다.

  • 클래스의 생성자는 비공개이므로 외부 코드에서 개체를 직접 인스턴스화할 수 없습니다.
  • 클래스 내부에 정적 인스턴스를 제공하고 필요할 때 이 인스턴스를 반환합니다.
  • 일반적으로 정적 공개 액세스 방법을 제공합니다.getInstance(), 클래스의 유일한 인스턴스를 얻는 데 사용됩니다.
2. 기본 구현

먼저 스레드 안전성 문제를 고려하지 않고 간단한 싱글톤 패턴 구현을 살펴보겠습니다.

#include <iostream>

class Singleton {
private:
    // 私有构造函数,防止外部实例化
    Singleton() {}

    // 私有拷贝构造函数和赋值操作符,防止拷贝
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 静态实例
    static Singleton* instance;

public:
    // 静态方法,返回类的唯一实例
    static Singleton* getInstance() {
        if (!instance) {
            instance = new Singleton();
        }
        return instance;
    }

    // 示例方法
    void doSomething() {
        std::cout << "Doing something..." << std::endl;
    }

    // 析构函数(通常是protected或public,取决于是否需要外部delete)
    ~Singleton() {
        std::cout << "Singleton destroyed." << std::endl;
    }
};

// 初始化静态实例
Singleton* Singleton::instance = nullptr;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();

    if (s1 == s2) {
        std::cout << "s1 and s2 are the same instance." << std::endl;
    }

    s1->doSomething();
    // 注意:在多线程环境下,上述实现可能存在安全问题

    // 通常不推荐手动删除单例对象,除非有特别理由
    // delete Singleton::instance; // 谨慎使用

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
3. 스레드 안전성 구현

다중 스레드 환경에서 위 구현으로 인해 여러 스레드가 동시에 들어갈 수 있습니다.getInstance() 메서드를 사용하여 인스턴스를 여러 번 생성합니다.이 문제를 해결하기 위해 뮤텍스 잠금(예:std::mutex) 스레드 안전을 보장하기 위해:

#include <mutex>
#include <iostream>

class Singleton {
private:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* instance;
    static std::mutex mtx;

public:
    static Singleton* getInstance() {
        std::lock_guard<std::mutex> lock(mtx);
        if (!instance) {
            instance = new Singleton();
        }
        return instance;
    }

    void doSomething() {
        std::cout << "Doing something..." << std::endl;
    }

    ~Singleton() {
        std::cout << "Singleton destroyed." << std::endl;
    }
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

// main函数保持不变
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
4. 스마트 포인터를 사용하여 싱글톤 수명 주기 관리

싱글톤 객체(즉, 프로그램 종료 시 자동으로 소멸됨)의 수명 주기를 자동으로 관리하기 위해 스마트 포인터(예:std::unique_ptr) 원시 포인터 대신:

#include <memory>
#include <mutex>
#include <iostream>

class Singleton {
private:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static std::unique_ptr<Singleton> instance;
    static std::mutex mtx;

public:
    static Singleton& getInstance() {
        std::lock_guard<std::mutex> lock(mtx);
        if (!instance) {
            instance = std::make_unique<Singleton>();
        }
        return *instance;
    }

    void doSomething() {
        std::cout << "Doing something..." << std::endl;
    }

    // 析构函数被智能指针管理,无需手动调用
    ~Singleton() {
        std::cout << "Singleton destroyed." << std::endl;
    }

    // 禁止拷贝和移动
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;
};

std::unique_ptr<Singleton> Singleton::instance = nullptr;
std::mutex Singleton::mtx;

int main() {
    // 注意这里返回的是引用,无需使用指针
    Singleton& s1 = Singleton::getInstance();
    Singleton& s2 = Singleton::getInstance();

    if (&s1 == &s2) {
        std::cout << "s1 and s2 are the same instance." << std::endl;
    }

    s1.doSomething();

    // 程序结束时,智能指针会自动销毁Singleton实例

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
5. 사용std::call_once최적화

C++11부터 시작하여,std::call_once 다중 스레드 환경에서도 함수가 한 번만 호출되도록 보다 효율적이고 간결한 방법을 제공합니다. 이 기능을 사용하여 싱글톤 패턴 구현을 더욱 최적화할 수 있습니다.

#include <memory>
#include <mutex>
#include <iostream>
#include <once.h> // 注意:实际上应使用#include <mutex>中的std::call_once

class Singleton {
private:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static std::unique_ptr<Singleton> instance;
    static std::once_flag onceFlag;

    static void createInstance() {
        instance = std::make_unique<Singleton>();
    }

public:
    static Singleton& getInstance() {
        std::call_once(onceFlag, createInstance);
        return *instance;
    }

    void doSomething() {
        std::cout << "Doing something..." << std::endl;
    }

    // 析构函数和移动操作符的禁用同上
};

std::unique_ptr<Singleton> Singleton::instance = nullptr;
std::once_flag Singleton::onceFlag;

// main函数保持不变
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

참고: 위 코드에서는 잘못 인용했습니다.#include <once.h>, 실제로 사용해야합니다<mutex>헤더 파일에std::once_flag그리고std::call_once

6. 요약

싱글톤 패턴은 C++에서 매우 유용한 디자인 패턴으로, 클래스에 인스턴스가 하나만 있고 전역 액세스 지점을 제공합니다. 그러나 싱글톤 패턴을 구현할 때, 특히 멀티스레드 환경에서 스레드 안전성 문제에 주의해야 합니다.뮤텍스 잠금, 스마트 포인터 및std::call_once최신 C++ 기능을 사용하면 싱글톤 패턴을 보다 안전하고 효율적으로 구현할 수 있습니다.

싱글톤 패턴을 설계할 때 싱글톤 객체의 수명주기 관리(프로그램 종료 시 자동으로 소멸되어야 하는지 여부), 지연 로딩 허용 여부(즉, 생성 지연)와 같은 몇 가지 추가 요소를 고려해야 합니다. 처음 사용될 때까지 인스턴스) 등 또한 경우에 따라 다중 인스턴스 패턴(클래스 인스턴스 수를 제어하지만 특정 상한을 초과하지 않음) 또는 컨텍스트 기반 싱글톤 패턴( 이는 다른 컨텍스트에 따라 다른 값을 반환합니다).

이 기사가 독자들이 C++의 싱글톤 패턴을 더 잘 이해하고 실제 프로젝트에서 유연하게 사용하는 데 도움이 되기를 바랍니다.