Κοινή χρήση τεχνολογίας

Σχεδιασμός και υλοποίηση του μοτίβου Singleton στο C: Από τα βασικά στα προχωρημένα

2024-07-12

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

Σχεδιασμός και υλοποίηση μοτίβου singleton σε C++: από τα βασικά έως τα προχωρημένα

Title: Σε βάθος κατανόηση του μοτίβου singleton στη C++: σχεδιασμός, υλοποίηση και εφαρμογή

Στην ανάπτυξη λογισμικού, τα πρότυπα σχεδίασης είναι αποδεδειγμένες λύσεις σε συγκεκριμένα προβλήματα. Βοηθούν τους προγραμματιστές να δημιουργήσουν λογισμικό με πιο συνεπή, επαναχρησιμοποιήσιμο και συντηρήσιμο τρόπο. Το Singleton Pattern είναι ένα από τα πιο βασικά και ευρέως χρησιμοποιούμενα μοτίβα σχεδίασης Διασφαλίζει ότι μια κλάση έχει μόνο μία παρουσία και παρέχει ένα παγκόσμιο σημείο πρόσβασης για την απόκτηση αυτής της παρουσίας.Αυτό το άρθρο θα εξετάσει σε βάθος πώς να εφαρμόσετε το μοτίβο Singleton στη C++, από τη βασική υλοποίηση έως τις εκδόσεις που είναι ασφαλείς για νήμα έως τις σύγχρονες λειτουργίες της C++, όπως έξυπνους δείκτες καιstd::call_once) εφαρμογή και προσπαθούν να παρέχουν στους αναγνώστες έναν περιεκτικό και πρακτικό οδηγό.

1. Βασικές έννοιες του μονότονου μοτίβου

Η βασική ιδέα του μοτίβου singleton είναι να διασφαλίσει ότι μια κλάση έχει μόνο μία παρουσία και παρέχει ένα παγκόσμιο σημείο πρόσβασης. Αυτή η λειτουργία είναι χρήσιμη όταν χρειάζεται να ελέγχετε την πρόσβαση σε πόρους (όπως προγράμματα ανάγνωσης αρχείων διαμόρφωσης, καταγραφείς, ομάδες σύνδεσης βάσεων δεδομένων, κ.λπ.). Το κλειδί για το μοτίβο singleton είναι:

  • Ο κατασκευαστής μιας κλάσης είναι ιδιωτικός, εμποδίζοντας τον εξωτερικό κώδικα να δημιουργήσει άμεσα το αντικείμενο.
  • Παρέχετε ένα στατικό στιγμιότυπο μέσα στην τάξη και επιστρέψτε αυτό το στιγμιότυπο όταν χρειάζεται.
  • Παρέχετε μια στατική μέθοδο δημόσιας πρόσβασης, συνήθως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() μέθοδο και δημιουργία παρουσιών πολλές φορές.Για να λύσουμε αυτό το πρόβλημα, μπορούμε να χρησιμοποιήσουμε μια κλειδαριά mutex (όπως π.χ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. Χρησιμοποιήστε έξυπνους δείκτες για να διαχειριστείτε τον κύκλο ζωής ενός παιδιού

Προκειμένου να διαχειριστούμε αυτόματα τον κύκλο ζωής ενός αντικειμένου singleton (δηλαδή που καταστρέφεται αυτόματα στο τέλος του προγράμματος), μπορούμε να χρησιμοποιήσουμε έξυπνους δείκτες (όπως π.χ.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 Παρέχει έναν πιο αποτελεσματικό και συνοπτικό τρόπο για να διασφαλιστεί ότι μια συνάρτηση καλείται μόνο μία φορά, ακόμη και σε περιβάλλον πολλαπλών νημάτων. Μπορούμε να χρησιμοποιήσουμε αυτή τη δυνατότητα για να βελτιστοποιήσουμε περαιτέρω την υλοποίηση του μοτίβου 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

ΣΗΜΕΙΩΣΗ: Στον παραπάνω κώδικα, παρέθεσα λάθος#include <once.h>, πρέπει πραγματικά να χρησιμοποιήσει κανείς<mutex>στο αρχείο κεφαλίδαςstd::once_flagκαιstd::call_once

6. Περίληψη

Το μοτίβο singleton είναι ένα πολύ χρήσιμο μοτίβο σχεδίασης στη C++ Διασφαλίζει ότι μια κλάση έχει μόνο μία παρουσία και παρέχει ένα παγκόσμιο σημείο πρόσβασης. Ωστόσο, πρέπει να δώσετε προσοχή σε ζητήματα ασφάλειας νημάτων κατά την εφαρμογή του μοτίβου singleton, ειδικά σε περιβάλλον πολλαπλών νημάτων.Χρησιμοποιώντας κλειδαριές mutex, έξυπνους δείκτες καιstd::call_onceΜε τις σύγχρονες δυνατότητες C++, μπορούμε να εφαρμόσουμε το μοτίβο singleton με μεγαλύτερη ασφάλεια και αποτελεσματικότητα.

Όταν σχεδιάζετε το μοτίβο singleton, πρέπει να λάβετε υπόψη ορισμένους πρόσθετους παράγοντες, όπως τη διαχείριση του κύκλου ζωής του αντικειμένου singleton (αν πρέπει να καταστραφεί αυτόματα όταν τελειώσει το πρόγραμμα), εάν επιτρέπεται η τεμπέλικη φόρτωση (δηλαδή η καθυστέρηση της δημιουργίας μιας περίπτωσης μέχρι να χρησιμοποιηθεί για πρώτη φορά) κ.λπ. Επιπλέον, σε ορισμένες περιπτώσεις, μπορεί να θέλετε να εξετάσετε παραλλαγές του μονότονου μοτίβου, όπως το μοτίβο πολλαπλών περιπτώσεων (το οποίο ελέγχει τον αριθμό των παρουσιών κλάσης, αλλά δεν υπερβαίνει ένα ορισμένο ανώτατο όριο) ή το μοτίβο μονής γραμμής που βασίζεται στο περιβάλλον ( που επιστρέφει διαφορετικές τιμές με βάση διαφορετικά περιβάλλοντα).

Ελπίζω ότι αυτό το άρθρο μπορεί να βοηθήσει τους αναγνώστες να κατανοήσουν καλύτερα το μοτίβο singleton στη C++ και να το χρησιμοποιήσουν με ευελιξία σε πραγματικά έργα.