Berbagi teknologi

Desain dan Implementasi Pola Singleton di C: Dari Dasar hingga Mahir

2024-07-12

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

Desain dan implementasi pola tunggal di C++: dari dasar hingga lanjutan

Judul: Pemahaman mendalam tentang pola tunggal di C++: desain, implementasi, dan aplikasi

Dalam pengembangan perangkat lunak, pola desain merupakan solusi yang terbukti untuk masalah tertentu. Pola desain membantu pengembang membangun perangkat lunak dengan cara yang lebih konsisten, dapat digunakan kembali, dan dipelihara. Pola Singleton adalah salah satu pola desain yang paling dasar dan banyak digunakan. Pola ini memastikan bahwa suatu kelas hanya memiliki satu instance dan menyediakan titik akses global untuk mendapatkan instance ini.Artikel ini akan membahas secara mendalam cara mengimplementasikan pola Singleton di C++, mulai dari implementasi dasar hingga versi thread-safe hingga fitur C++ modern seperti smart pointer danstd::call_once) aplikasi, dan berusaha untuk memberikan panduan yang komprehensif dan praktis kepada pembaca.

1. Konsep dasar pola tunggal

Ide inti dari pola singleton adalah untuk memastikan bahwa suatu kelas hanya memiliki satu instance dan menyediakan titik akses global. Mode ini berguna ketika Anda perlu mengontrol akses ke sumber daya (seperti pembaca file konfigurasi, logger, kumpulan koneksi database, dll.). Kunci dari pola tunggal adalah:

  • Konstruktor suatu kelas bersifat pribadi, mencegah kode eksternal membuat instance objek secara langsung.
  • Berikan instance statis di dalam kelas dan kembalikan instance ini bila diperlukan.
  • Biasanya menyediakan metode akses publik statisgetInstance(), digunakan untuk mendapatkan satu-satunya instance dari suatu kelas.
2. Implementasi dasar

Pertama, mari kita lihat implementasi pola tunggal sederhana tanpa mempertimbangkan masalah keamanan thread:

#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. Penerapan keamanan benang

Dalam lingkungan multi-utas, penerapan di atas dapat menyebabkan beberapa utas masuk secara bersamaangetInstance() metode dan membuat instance beberapa kali.Untuk mengatasi masalah ini, kita dapat menggunakan kunci mutex (sepertistd::mutex) untuk memastikan keamanan benang:

#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. Gunakan petunjuk cerdas untuk mengelola siklus hidup tunggal

Untuk mengatur siklus hidup objek tunggal secara otomatis (yaitu dimusnahkan secara otomatis di akhir program), kita dapat menggunakan smart pointer (sepertistd::unique_ptr) alih-alih petunjuk mentah:

#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. Gunakanstd::call_onceoptimasi

Dimulai dengan C++11,std::call_once Memberikan cara yang lebih efisien dan ringkas untuk memastikan bahwa suatu fungsi dipanggil hanya sekali, bahkan dalam lingkungan multi-thread. Kita dapat menggunakan fitur ini untuk lebih mengoptimalkan implementasi pola tunggal:

#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

CATATAN: Pada kode di atas, saya salah mengutip#include <once.h>, seseorang harus benar-benar menggunakannya<mutex>di file headerstd::once_flagDanstd::call_once

6. Ringkasan

Pola singleton adalah pola desain yang sangat berguna dalam C++. Pola ini memastikan bahwa suatu kelas hanya memiliki satu instance dan menyediakan titik akses global. Namun, Anda perlu memperhatikan masalah keamanan thread saat menerapkan pola singleton, terutama di lingkungan multi-thread.Dengan menggunakan kunci mutex, smart pointer danstd::call_onceDengan fitur C++ modern, kita dapat mengimplementasikan pola tunggal dengan lebih aman dan efisien.

Saat merancang pola tunggal, Anda perlu mempertimbangkan beberapa faktor tambahan, seperti manajemen siklus hidup objek tunggal (apakah objek tersebut perlu dimusnahkan secara otomatis saat program berakhir), apakah pemuatan lambat diperbolehkan (yaitu, menunda pembuatan dari sebuah instance sampai pertama kali digunakan), dll. Selain itu, dalam beberapa kasus, Anda mungkin ingin mempertimbangkan variasi pola tunggal, seperti pola multi-instance (yang mengontrol jumlah instance kelas, namun tidak melebihi batas atas tertentu) atau pola singleton berbasis konteks ( yang mengembalikan nilai berbeda berdasarkan konteks berbeda).

Saya harap artikel ini dapat membantu pembaca lebih memahami pola tunggal di C++ dan menggunakannya secara fleksibel dalam proyek sebenarnya.