Teknologian jakaminen

Singleton Patternin suunnittelu ja toteutus C:ssä: Perusasioista edistyneisiin

2024-07-12

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

Yksittäisen kuvion suunnittelu ja toteutus C++:ssa: perusasioista edistyneeseen

Otsikko: Singleton-mallin syvällinen ymmärtäminen C++:ssa: suunnittelu, toteutus ja sovellus

Ohjelmistokehityksessä suunnittelumallit ovat todistettuja ratkaisuja tiettyihin ongelmiin. Ne auttavat kehittäjiä rakentamaan ohjelmistoja johdonmukaisemmalla, uudelleenkäytettävällä ja ylläpidettävällä tavalla. Singleton Pattern on yksi yksinkertaisimmista ja laajimmin käytetyistä suunnittelumalleista. Se varmistaa, että luokalla on vain yksi ilmentymä, ja tarjoaa maailmanlaajuisen yhteyspisteen tämän ilmentymän saamiseksi.Tässä artikkelissa tarkastellaan perusteellisesti Singleton-mallin toteuttamista C++:ssa perustoteutuksesta lankaturvallisiin versioihin nykyaikaisiin C++-ominaisuuksiin, kuten älykkäisiin osoittimiin jastd::call_once) -sovellusta ja pyrimme tarjoamaan lukijoille kattavan ja käytännöllisen oppaan.

1. Yksittäiskuvion peruskäsitteet

Singleton-mallin ydinajatus on varmistaa, että luokassa on vain yksi esiintymä ja tarjota globaali tukiasema. Tämä tila on hyödyllinen, kun haluat hallita resurssien käyttöä (kuten määritystiedostojen lukijat, lokerit, tietokantayhteysvarastot jne.). Avain singleton malliin on:

  • Luokan rakentaja on yksityinen, mikä estää ulkoista koodia luomasta objektia suoraan.
  • Anna staattinen ilmentymä luokan sisällä ja palauta tämä ilmentymä tarvittaessa.
  • Tarjoa yleensä staattinen julkinen käyttötapagetInstance(), jota käytetään luokan ainoan esiintymän hankkimiseen.
2. Perustoteutus

Tarkastellaan ensin yksinkertaista yksittäiskuvion toteutusta ottamatta huomioon kierteiden turvallisuusongelmia:

#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. Kierteen turvatoteutus

Monisäikeisessä ympäristössä yllä oleva toteutus voi aiheuttaa useiden säikeiden saapumisen samanaikaisestigetInstance() menetelmää ja luoda esiintymiä useita kertoja.Tämän ongelman ratkaisemiseksi voimme käyttää mutex-lukkoa (esimstd::mutex) langan turvallisuuden varmistamiseksi:

#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. Käytä älykkäitä osoittimia yksittäisten elinkaaren hallintaan

Yksittäisen objektin (eli automaattisesti tuhoutuneen ohjelman lopussa) elinkaaren hallintaan voidaan käyttää älykkäitä osoittimia (esim.std::unique_ptr) raaka-osoittimien sijaan:

#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. Käytästd::call_onceoptimointi

Alkaen C++11:stä,std::call_once Tarjoaa tehokkaamman ja tiiviimmän tavan varmistaa, että funktiota kutsutaan vain kerran, jopa monisäikeisessä ympäristössä. Voimme käyttää tätä ominaisuutta optimoidaksemme edelleen singleton-mallin toteutusta:

#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

HUOMAA: Lainasin yllä olevassa koodissa väärin#include <once.h>, pitäisi itse asiassa käyttää<mutex>otsikkotiedostossastd::once_flagjastd::call_once

6. Yhteenveto

Singleton-malli on erittäin hyödyllinen suunnittelumalli C++:ssa. Se varmistaa, että luokassa on vain yksi esiintymä ja se tarjoaa maailmanlaajuisen tukiaseman. Sinun on kuitenkin kiinnitettävä huomiota lankojen turvallisuuteen, kun otat käyttöön singleton-kuvion, erityisesti monisäikeisessä ympäristössä.Käyttämällä mutex-lukkoja, älykkäitä osoittimia jastd::call_onceNykyaikaisilla C++-ominaisuuksilla voimme toteuttaa singleton-kuvion turvallisemmin ja tehokkaammin.

Singleton-mallia suunniteltaessa on otettava huomioon myös joitain lisätekijöitä, kuten singleton-objektin elinkaaren hallinta (pitääkö se tuhota automaattisesti ohjelman päättyessä), onko laiska lataus sallittu (eli ilmentymän luominen, kunnes sitä käytetään ensimmäistä kertaa) jne. Lisäksi joissain tapauksissa saatat haluta harkita muunnelmia yksittäisestä mallista, kuten usean esiintymän mallista (joka ohjaa luokkaesiintymien määrää, mutta ei ylitä tiettyä ylärajaa) tai kontekstipohjaista yksittäismallia ( joka palauttaa eri arvot eri kontekstien perusteella).

Toivon, että tämä artikkeli auttaa lukijoita ymmärtämään C++:n singleton-mallia paremmin ja käyttämään sitä joustavasti todellisissa projekteissa.