Partage de technologie

Conception et implémentation de modèle Singleton en C : des bases à l'avancé

2024-07-12

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

Conception et implémentation de pattern singleton en C++ : des bases à l'avancé

Titre : Compréhension approfondie du modèle singleton en C++ : conception, implémentation et application

Dans le développement de logiciels, les modèles de conception sont des solutions éprouvées à des problèmes spécifiques. Ils aident les développeurs à créer des logiciels de manière plus cohérente, réutilisable et maintenable. Le modèle Singleton est l'un des modèles de conception les plus basiques et les plus utilisés. Il garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global pour obtenir cette instance.Cet article examinera en profondeur comment implémenter le modèle Singleton en C++, de l'implémentation de base aux versions thread-safe en passant par les fonctionnalités C++ modernes telles que les pointeurs intelligents etstd::call_once) et nous nous efforçons de fournir aux lecteurs un guide complet et pratique.

1. Concepts de base du modèle singleton

L'idée principale du modèle singleton est de garantir qu'une classe n'a qu'une seule instance et de fournir un point d'accès global. Ce mode est utile lorsque vous devez contrôler l'accès aux ressources (telles que les lecteurs de fichiers de configuration, les enregistreurs, les pools de connexions à la base de données, etc.). La clé du modèle singleton est :

  • Le constructeur d'une classe est privé, empêchant le code externe d'instancier directement l'objet.
  • Fournissez une instance statique à l’intérieur de la classe et renvoyez cette instance si nécessaire.
  • Fournir une méthode d'accès public statique, généralementgetInstance(), utilisé pour obtenir la seule instance d'une classe.
2. Mise en œuvre de base

Tout d’abord, examinons une implémentation simple d’un modèle singleton sans tenir compte des problèmes de sécurité des threads :

#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. Implémentation de la sécurité des threads

Dans un environnement multithread, l'implémentation ci-dessus peut entraîner l'entrée de plusieurs threads en même temps.getInstance() méthode et créez des instances plusieurs fois.Pour résoudre ce problème, nous pouvons utiliser un verrou mutex (commestd::mutex) pour garantir la sécurité des threads :

#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. Utilisez des pointeurs intelligents pour gérer le cycle de vie des singletons

Afin de gérer automatiquement le cycle de vie d'un objet singleton (c'est-à-dire automatiquement détruit à la fin du programme), on peut utiliser des pointeurs intelligents (tels questd::unique_ptr) au lieu de pointeurs bruts :

#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. Utiliserstd::call_onceoptimisation

À partir de C++11,std::call_once Fournit un moyen plus efficace et plus concis de garantir qu’une fonction n’est appelée qu’une seule fois, même dans un environnement multithread. Nous pouvons utiliser cette fonctionnalité pour optimiser davantage la mise en œuvre du modèle singleton :

#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

REMARQUE : dans le code ci-dessus, j'ai cité à tort#include <once.h>, il faut en fait utiliser<mutex>dans le fichier d'en-têtestd::once_flagetstd::call_once

6. Résumé

Le modèle singleton est un modèle de conception très utile en C++. Il garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global. Cependant, vous devez faire attention aux problèmes de sécurité des threads lors de la mise en œuvre du modèle singleton, en particulier dans un environnement multithread.En utilisant des verrous mutex, des pointeurs intelligents etstd::call_onceAvec les fonctionnalités C++ modernes, nous pouvons implémenter le modèle singleton de manière plus sûre et plus efficace.

Lors de la conception du modèle singleton, vous devez également prendre en compte certains facteurs supplémentaires, tels que la gestion du cycle de vie de l'objet singleton (s'il doit être automatiquement détruit à la fin du programme), si le chargement paresseux est autorisé (c'est-à-dire retarder la création d'une instance jusqu'à sa première utilisation), etc. De plus, dans certains cas, vous souhaiterez peut-être envisager des variantes du modèle singleton, telles que le modèle multi-instance (qui contrôle le nombre d'instances de classe, mais ne dépasse pas une certaine limite supérieure) ou le modèle singleton basé sur le contexte ( qui renvoie différentes valeurs en fonction de différents contextes).

J'espère que cet article pourra aider les lecteurs à mieux comprendre le modèle singleton en C++ et à l'utiliser de manière flexible dans des projets réels.