技術共有

C でのシングルトン パターンの設計と実装: 基本から高度まで

2024-07-12

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

C++ でのシングルトン パターンの設計と実装: 基本から高度まで

タイトル: C++ のシングルトン パターンの深い理解: 設計、実装、アプリケーション

ソフトウェア開発では、設計パターンは、開発者がより一貫性があり、再利用可能で、保守可能な方法でソフトウェアを構築するのに役立つ、特定の問題に対する実証済みの解決策です。シングルトン パターンは、最も基本的で広く使用されている設計パターンの 1 つであり、クラスにインスタンスが 1 つだけ存在することを保証し、このインスタンスを取得するためのグローバル アクセス ポイントを提供します。この記事では、基本的な実装からスレッド セーフ バージョン、スマート ポインターや最新の C++ 機能まで、C++ でシングルトン パターンを実装する方法を詳しく説明します。std::call_once) アプリケーションを開発し、読者に包括的で実践的なガイドを提供するよう努めています。

1. シングルトンパターンの基本概念

シングルトン パターンの中心的な考え方は、クラスにインスタンスが 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マルチスレッド環境でも関数が 1 回だけ呼び出されるようにする、より効率的かつ簡潔な方法を提供します。この機能を使用して、シングルトン パターンの実装をさらに最適化できます。

#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++ の非常に便利な設計パターンです。これにより、クラスにインスタンスが 1 つだけ存在し、グローバル アクセス ポイントが提供されます。ただし、特にマルチスレッド環境でシングルトン パターンを実装する場合は、スレッド セーフティの問題に注意する必要があります。ミューテックスロック、スマートポインタ、std::call_once最新の C++ 機能を使用すると、シングルトン パターンをより安全かつ効率的に実装できます。

シングルトン パターンを設計するときは、シングルトン オブジェクトのライフサイクル管理 (プログラム終了時に自動的に破棄する必要があるかどうか)、遅延読み込みが許可されているかどうか (つまり、作成を遅らせるかどうか) など、いくつかの追加要素を考慮する必要があります。最初に使用されるまでのインスタンスの)など。さらに、場合によっては、マルチインスタンス パターン (クラス インスタンスの数を制御しますが、特定の上限を超えない) やコンテキスト ベースのシングルトン パターン (異なるコンテキストに基づいて異なる値を返します)。

この記事が、読者が C++ のシングルトン パターンをより深く理解し、実際のプロジェクトで柔軟に使用できるようになれば幸いです。