Baru-baru ini saya bertemu dengan realisasi / implementasi pola desain Singleton untuk C ++. Itu terlihat seperti ini (saya telah mengadopsi dari contoh kehidupan nyata):
// a lot of methods are omitted here
class Singleton
{
public:
static Singleton* getInstance( );
~Singleton( );
private:
Singleton( );
static Singleton* instance;
};
Dari deklarasi ini saya dapat menyimpulkan bahwa bidang instance diinisiasi di heap. Itu berarti ada alokasi memori. Apa yang benar-benar tidak jelas bagi saya adalah kapan tepatnya memori itu akan dialokasikan? Atau ada kebocoran bug dan memori? Sepertinya ada masalah dalam implementasinya.
Pertanyaan utama saya adalah, bagaimana cara menerapkannya dengan cara yang benar?
c++
design-patterns
singleton
Artem Barger
sumber
sumber
Jawaban:
Pada tahun 2008 saya memberikan implementasi C ++ 98 dari pola desain Singleton yang dievaluasi malas, dijamin kehancuran, tidak aman secara teknis:
Bisakah ada yang memberi saya sampel Singleton di c ++?
Berikut ini adalah implementasi C ++ 11 yang diperbarui dari pola desain Singleton yang dievaluasi dengan malas, dihancurkan dengan benar, dan aman dari benang .
Lihat artikel ini tentang kapan menggunakan singleton: (tidak sering)
Singleton: Bagaimana seharusnya digunakan
Lihat dua artikel ini tentang urutan inisialisasi dan cara mengatasinya:
Urutan inisialisasi variabel statis
Menemukan masalah C + + inisialisasi statis
Lihat artikel ini yang menggambarkan masa hidup:
Apa masa pakai variabel statis dalam fungsi C ++?
Lihat artikel ini yang membahas beberapa implikasi threading untuk lajang:
Contoh Singleton dideklarasikan sebagai variabel statis metode GetInstance, apakah ini aman untuk thread?
Lihat artikel ini yang menjelaskan mengapa penguncian ganda tidak akan bekerja pada C ++:
Apa saja perilaku tak terdefinisi umum yang harus diketahui oleh seorang programmer C ++?
Dr Dobbs: C ++ dan The Perils of Double-Checked Locking: Bagian I
sumber
What irks me most though is the run-time check of the hidden boolean in getInstance()
Itu adalah asumsi pada teknik implementasi. Tidak perlu ada asumsi bahwa itu hidup. lihat stackoverflow.com/a/335746/14065 Anda dapat memaksa suatu situasi agar selalu hidup (lebih sedikit overhead daripadaSchwarz counter
). Variabel global memiliki lebih banyak masalah dengan pesanan inisialisasi (lintas unit kompilasi) karena Anda tidak memaksakan pesanan. Kelebihan dari model ini adalah 1) inisialisasi malas. 2) Kemampuan untuk menegakkan perintah (Schwarz membantu tetapi lebih buruk). Yapget_instance()
jauh lebih jelek.Menjadi seorang Singleton, Anda biasanya tidak ingin itu dihancurkan.
Ini akan dirobohkan dan didelokasi ketika program berakhir, yang merupakan perilaku normal yang diinginkan untuk seorang lajang. Jika Anda ingin dapat membersihkannya secara eksplisit, cukup mudah untuk menambahkan metode statis ke kelas yang memungkinkan Anda untuk mengembalikannya ke kondisi bersih, dan mengalokasikannya kembali saat digunakan, tetapi itu di luar ruang lingkup suatu singleton "klasik".
sumber
Anda dapat menghindari alokasi memori. Ada banyak varian, semuanya memiliki masalah dalam hal lingkungan multithreading.
Saya lebih suka implementasi semacam ini (sebenarnya, tidak benar mengatakan saya lebih suka, karena saya menghindari lajang sebanyak mungkin):
Tidak memiliki alokasi memori dinamis.
sumber
Jawaban @Loki Astari luar biasa.
Namun ada kalanya dengan beberapa objek statis di mana Anda harus dapat menjamin bahwa singleton tidak akan dihancurkan sampai semua objek statis Anda yang menggunakan singleton tidak lagi membutuhkannya.
Dalam hal ini
std::shared_ptr
dapat digunakan untuk menjaga singleton tetap hidup untuk semua pengguna bahkan ketika destruktor statis dipanggil di akhir program:sumber
Alternatif non-alokasi lain: buat singleton, katakanlah kelas
C
, sesuai kebutuhan Anda:menggunakan
Baik ini maupun jawaban Cătălin secara otomatis aman di C ++ saat ini, tetapi akan berada di C ++ 0x.
sumber
Saya tidak menemukan implementasi CRTP di antara jawaban, jadi ini dia:
Untuk menggunakan cukup mewarisi kelas Anda dari ini, seperti:
class Test : public Singleton<Test>
sumber
Solusi dalam jawaban yang diterima memiliki kelemahan yang signifikan - destruktor untuk singleton dipanggil setelah kontrol meninggalkan
main()
fungsi. Mungkin ada masalah sebenarnya, ketika beberapa objek dependen dialokasikan di dalamnyamain
.Saya menemui masalah ini, ketika mencoba memperkenalkan Singleton di aplikasi Qt. Saya memutuskan, bahwa semua dialog pengaturan saya harus berupa Singletons, dan mengadopsi pola di atas. Sayangnya, kelas utama Qt
QApplication
dialokasikan pada stack di Internetmain
fungsi, dan Qt melarang membuat / menghancurkan dialog ketika tidak ada objek aplikasi yang tersedia.Itulah sebabnya saya lebih suka lajang yang dialokasikan untuk tumpukan. Saya memberikan metode
init()
dan eksplisitterm()
untuk semua lajang dan memanggil mereka di dalammain
. Jadi saya memiliki kontrol penuh atas urutan penciptaan / penghancuran lajang, dan saya juga menjamin bahwa lajang akan dibuat, tidak peduli apakah seseorang memanggilgetInstance()
atau tidak.sumber
Berikut ini adalah implementasi yang mudah.
Hanya satu objek yang dibuat dan referensi objek ini dikembalikan setiap dan sesudahnya.
Di sini 00915CB8 adalah lokasi memori Object singleton, sama untuk durasi program tetapi (biasanya!) Berbeda setiap kali program dijalankan.
NB Ini bukan thread yang aman. Anda harus memastikan keamanan thread.
sumber
Jika Anda ingin mengalokasikan objek di tumpukan, mengapa tidak menggunakan pointer unik. Memori juga akan dibatalkan alokasi karena kita menggunakan pointer unik.
sumber
m_s
lokalstatic
darigetInstance()
dan menginisialisasi segera tanpa tes.Memang mungkin dialokasikan dari tumpukan, tetapi tanpa sumber tidak ada cara untuk mengetahui.
Implementasi tipikal (diambil dari beberapa kode yang saya miliki di emacs) adalah:
... dan andalkan program keluar dari ruang lingkup untuk membersihkan sesudahnya.
Jika Anda bekerja pada platform di mana pembersihan harus dilakukan secara manual, saya mungkin akan menambahkan rutin pembersihan manual.
Masalah lain dengan melakukannya dengan cara ini adalah tidak aman-utas. Dalam lingkungan multithreaded, dua utas bisa melewati "jika" sebelum keduanya memiliki kesempatan untuk mengalokasikan contoh baru (jadi keduanya akan). Ini masih bukan masalah besar jika Anda mengandalkan penghentian program untuk membersihkan.
sumber
Adakah yang menyebutkan
std::call_once
danstd::once_flag
? Sebagian besar pendekatan lain - termasuk penguncian ganda diperiksa - rusak.Salah satu masalah utama dalam penerapan pola tunggal adalah inisialisasi yang aman. Satu-satunya cara yang aman adalah untuk menjaga urutan inisialisasi dengan hambatan sinkronisasi. Tetapi hambatan itu sendiri harus dimulai dengan aman.
std::once_flag
adalah mekanisme untuk mendapatkan inisialisasi aman yang dijamin.sumber
Kami membahas topik ini baru-baru ini di kelas EECS saya. Jika Anda ingin melihat catatan kuliah secara detail, kunjungi http://umich.edu/~eecs381/lecture/IdiomsDesPattsCreational.pdf
Ada dua cara yang saya tahu untuk membuat kelas Singleton dengan benar.
Cara pertama:
Terapkan itu mirip dengan cara Anda memilikinya dalam contoh Anda. Sedangkan untuk penghancuran, "Lajang biasanya bertahan lama selama program dijalankan; sebagian besar OS akan memulihkan memori dan sebagian besar sumber daya lainnya ketika sebuah program berakhir, sehingga ada argumen untuk tidak khawatir tentang hal ini."
Namun, adalah praktik yang baik untuk membersihkan pada penghentian program. Oleh karena itu, Anda dapat melakukan ini dengan kelas SingletonDestructor statis tambahan dan menyatakannya sebagai teman di Singleton Anda.
Singleton_destroyer akan dibuat pada startup program, dan "ketika program berakhir, semua objek global / statis dihancurkan oleh kode shutdown perpustakaan runtime (dimasukkan oleh linker), sehingga the_destroyer akan dihancurkan; destructor-nya akan menghapus Singleton, menjalankan destruktor. "
Jalan Kedua
Ini disebut Singleton Meyers, dibuat oleh penyihir C ++ Scott Meyers. Cukup tentukan get_instance () secara berbeda. Sekarang Anda juga dapat menyingkirkan variabel anggota penunjuk.
Ini rapi karena nilai yang dikembalikan adalah dengan referensi dan Anda dapat menggunakan
.
sintaks alih-alih->
mengakses variabel anggota."Kompiler secara otomatis membuat kode yang menciptakan 's' pertama kali melalui deklarasi, tidak setelahnya, dan kemudian menghapus objek statis pada penghentian program."
Perhatikan juga bahwa dengan Meyers Singleton Anda "dapat masuk ke situasi yang sangat sulit jika objek saling bergantung pada saat penghentian - kapan Singleton menghilang relatif terhadap objek lain? Tetapi untuk aplikasi sederhana, ini berfungsi dengan baik."
sumber
Selain diskusi lain di sini, mungkin perlu dicatat bahwa Anda dapat memiliki ke-global-an, tanpa membatasi penggunaan pada satu contoh. Sebagai contoh, pertimbangkan kasus referensi menghitung sesuatu ...
Sekarang di suatu tempat di dalam suatu fungsi (seperti
main
) yang dapat Anda lakukan:Wasit tidak perlu menyimpan pointer kembali ke masing-masing
Store
karena informasi tersebut diberikan pada waktu kompilasi. Anda juga tidak perlu khawatir tentang masaStore
pakai karena kompiler mengharuskan itu bersifat global. Jika memang hanya ada satu contohStore
maka tidak ada overhead dalam pendekatan ini; dengan lebih dari satu contoh terserah kompiler untuk menjadi pintar tentang pembuatan kode. Jika perlu,ItemRef
kelas bahkan dapat membuatfriend
dariStore
(Anda dapat memiliki teman templated!).Jika
Store
itu sendiri adalah kelas templated maka hal-hal menjadi berantakan, tetapi masih mungkin untuk menggunakan metode ini, mungkin dengan menerapkan kelas pembantu dengan tanda tangan berikut:Pengguna sekarang dapat membuat
StoreWrapper
jenis (dan instance global) untuk setiapStore
instance global , dan selalu mengakses toko melalui instance wrapper mereka (dengan demikian melupakan detail berdarah parameter templat yang diperlukan untuk menggunakanStore
).sumber
Ini tentang manajemen objek seumur hidup. Misalkan Anda memiliki lebih dari lajang dalam perangkat lunak Anda. Dan mereka bergantung pada Logger singleton. Selama penghancuran aplikasi, misalkan objek singleton lain menggunakan Logger untuk mencatat langkah-langkah penghancurannya. Anda harus menjamin bahwa Logger harus dibersihkan terakhir. Oleh karena itu, harap periksa juga makalah ini: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf
sumber
Implementasi saya mirip dengan Galik. Perbedaannya adalah implementasi saya memungkinkan pointer bersama untuk membersihkan memori yang dialokasikan, sebagai kebalikan dari memegang memori hingga aplikasi keluar dan pointer statis dibersihkan.
sumber
Kode Anda benar, kecuali bahwa Anda tidak mendeklarasikan pointer instance di luar kelas . Deklarasi kelas statis variabel statis tidak dianggap deklarasi dalam C ++, namun ini diizinkan dalam bahasa lain seperti C # atau Java dll.
Anda harus tahu bahwa instance Singleton tidak perlu dihapus secara manual oleh kami . Kita memerlukan satu objek dari itu di seluruh program, jadi pada akhir pelaksanaan program, itu akan secara otomatis dialokasikan.
sumber
Makalah yang dikaitkan dengan di atas menjelaskan kekurangan dari penguncian diperiksa ganda adalah bahwa kompiler dapat mengalokasikan memori untuk objek dan mengatur pointer ke alamat memori yang dialokasikan, sebelum konstruktor objek telah dipanggil. Namun sangat mudah dalam c ++ untuk menggunakan pengalokasi untuk mengalokasikan memori secara manual, dan kemudian menggunakan panggilan konstruk untuk menginisialisasi memori. Menggunakan appraoch ini, penguncian diperiksa dua kali berfungsi dengan baik.
sumber
Contoh:
sumber
Kelas singleton sederhana, Ini harus menjadi file kelas tajuk Anda
Akses singleton Anda seperti ini:
sumber
Saya pikir Anda harus menulis fungsi statis di mana objek statis Anda dihapus. Anda harus memanggil fungsi ini ketika Anda akan menutup aplikasi Anda. Ini akan memastikan Anda tidak mengalami kebocoran memori.
sumber