Kelas antarmuka sangat banyak digunakan dalam program C ++. Namun, sayangnya, sering terjadi kesalahan saat mengimplementasikan solusi berdasarkan kelas antarmuka. Artikel ini menjelaskan cara mendesain kelas antarmuka dengan benar, beberapa opsi dipertimbangkan. Penggunaan pointer cerdas dijelaskan secara rinci. Contoh implementasi kelas pengecualian dan templat kelas koleksi berdasarkan kelas antarmuka diberikan.
Daftar isi
Pendahuluan
Kelas antarmuka adalah kelas yang tidak memiliki data dan sebagian besar terdiri dari fungsi virtual murni. Solusi ini memungkinkan Anda untuk benar-benar memisahkan implementasi dari antarmuka - klien menggunakan kelas antarmuka - di tempat lain kelas turunan dibuat di mana fungsi virtual murni didefinisikan ulang dan fungsi pabrik didefinisikan. Detail implementasi sepenuhnya tersembunyi dari klien. Dengan cara ini, enkapsulasi benar diimplementasikan, yang tidak mungkin dengan kelas biasa. Anda dapat membaca tentang kelas antarmuka dari Scott Meyers [Meyers2]. Kelas antarmuka juga disebut kelas protokol.
Menggunakan kelas antarmuka memungkinkan Anda untuk melemahkan ketergantungan antara berbagai bagian proyek, yang menyederhanakan pengembangan tim dan mengurangi waktu kompilasi / perakitan. Kelas antarmuka membuatnya lebih mudah untuk mengimplementasikan solusi dinamis dan fleksibel ketika modul dimuat secara selektif saat runtime. Menggunakan kelas antarmuka sebagai perpustakaan antarmuka (API) (SDK) menyederhanakan solusi masalah kompatibilitas biner.
Kelas antarmuka digunakan cukup luas, dengan bantuan mereka mengimplementasikan antarmuka (API) perpustakaan (SDK), antarmuka plug-in (plugin) dan banyak lagi. Banyak pola Gang of Four [GoF] secara alami diimplementasikan menggunakan kelas antarmuka. Kelas antarmuka termasuk antarmuka COM. Namun, sayangnya, sering terjadi kesalahan saat mengimplementasikan solusi berdasarkan kelas antarmuka. Mari kita coba memperjelas masalah ini.
1. Fungsi anggota khusus, membuat dan menghapus objek
Bagian ini menjelaskan secara singkat sejumlah fitur C ++ yang perlu Anda ketahui untuk memahami sepenuhnya solusi yang ditawarkan untuk kelas antarmuka.
1.1. Fungsi Anggota Khusus
Jika programmer tidak mendefinisikan fungsi anggota kelas dari daftar berikut - konstruktor default, copy constructor, operator penugasan copy, destructor - maka kompiler dapat melakukan ini untuknya. C ++ 11 menambahkan move constructor dan operator penugasan move ke daftar ini. Fungsi anggota ini disebut fungsi anggota khusus. Mereka dihasilkan hanya jika digunakan, dan kondisi tambahan khusus untuk setiap fungsi terpenuhi. Kami menarik perhatian pada fakta bahwa penggunaan ini ternyata cukup tersembunyi (misalnya, ketika menerapkan warisan). Jika fungsi yang diperlukan tidak dapat dibuat, kesalahan dihasilkan. (Dengan pengecualian operasi relokasi, mereka digantikan oleh operasi penyalinan.) Fungsi anggota yang dihasilkan oleh kompilator bersifat publik dan dapat di-embed.
Fungsi anggota khusus tidak diwariskan, jika fungsi anggota khusus diperlukan di kelas turunan, kompiler akan selalu mencoba untuk menghasilkannya, keberadaan fungsi anggota terkait yang didefinisikan di kelas dasar oleh pemrogram tidak memengaruhi hal ini.
Programmer dapat melarang pembuatan fungsi anggota khusus, dalam C ++ 11 perlu menggunakan konstruk "=delete"
ketika mendeklarasikan, dalam C ++ 98 mendeklarasikan fungsi anggota terkait pribadi dan tidak mendefinisikan. Dalam pewarisan kelas, larangan menghasilkan fungsi anggota khusus yang dibuat di kelas dasar berlaku untuk semua kelas turunan.
Jika pemrogram merasa nyaman dengan fungsi anggota yang dihasilkan oleh kompiler, maka dalam C ++ 11 ia dapat menunjukkan ini secara eksplisit, dan tidak hanya menjatuhkan deklarasi. Untuk melakukan ini, Anda harus menggunakan konstruksi "=default"
ketika mendeklarasikan, sementara kode lebih baik dibaca dan fitur tambahan yang terkait dengan manajemen tingkat akses muncul.
Detail tentang fungsi anggota khusus dapat ditemukan di [Meyers3].
1.2. Membuat dan menghapus objek - detail dasar
Membuat dan menghapus objek menggunakan operator new/delete
adalah operasi dua-dalam-satu yang khas. Saat memanggil new
, memori pertama dialokasikan untuk objek. Jika seleksi berhasil, konstruktor dipanggil. Jika konstruktor melempar pengecualian, maka memori yang dialokasikan dibebaskan. Ketika operator delete
dipanggil, semuanya terjadi dalam urutan terbalik: pertama, destruktor dipanggil, lalu memori dibebaskan. Destruktor seharusnya tidak melempar pengecualian.
Jika operator new
digunakan untuk membuat array objek, maka memori pertama dialokasikan untuk seluruh array. Jika seleksi berhasil, maka konstruktor default dipanggil untuk setiap elemen array mulai dari nol. Jika ada konstruktor yang melempar pengecualian, maka untuk semua elemen array yang dibuat, destruktor dipanggil dalam urutan terbalik dari panggilan konstruktor, maka memori yang dialokasikan akan dibebaskan. Untuk menghapus array, Anda harus memanggil operator delete[]
(disebut operator delete
untuk array), dan untuk semua elemen array, destructor disebut dalam urutan terbalik dari konstruktor, kemudian memori yang dialokasikan dibebaskan.
Perhatian! Anda harus memanggil bentuk yang benar dari operator delete
, tergantung pada apakah satu objek atau array dihapus. Aturan ini harus dipatuhi dengan ketat, jika tidak Anda bisa mendapatkan perilaku yang tidak terdefinisi, yaitu, apa pun bisa terjadi: kebocoran memori, kerusakan, dll. Lihat [Meyers2] untuk detailnya.
Fungsi alokasi memori standar std::bad_alloc
memenuhi permintaan melempar pengecualian tipe std::bad_alloc
.
Aman untuk menerapkan segala bentuk operator delete
ke pointer nol.
Dalam uraian di atas, satu klarifikasi diperlukan. Untuk yang disebut tipe sepele (tipe bawaan, struktur gaya C), konstruktor mungkin tidak dipanggil, dan destruktor tidak melakukan apa pun dalam hal apa pun. Lihat juga bagian 1.6.
1.3. Tingkat akses destruktor
Ketika operator delete
diterapkan ke pointer ke kelas, destruktor kelas itu harus tersedia di titik panggilan delete
. (Ada beberapa pengecualian untuk aturan ini, dibahas di Bagian 1.6.) Dengan demikian, dengan membuat destruktor aman atau tertutup, programmer melarang penggunaan operator delete
mana destruktor tidak tersedia. Ingatlah bahwa jika tidak ada destruktor didefinisikan di kelas, kompiler akan melakukan ini sendiri, dan destruktor ini akan terbuka (lihat bagian 1.1).
1.4. Buat dan hapus dalam satu modul
Jika operator new
membuat objek, maka operator delete
harus dalam modul yang sama untuk delete
. Secara kiasan, "letakkan di mana Anda membawanya." Aturan ini terkenal, lihat, misalnya, [Sutter / Alexandrescu]. Jika aturan ini dilanggar, βketidakcocokanβ fungsi mengalokasikan dan membebaskan memori dapat terjadi, yang, sebagai suatu peraturan, mengarah pada penghentian program yang tidak normal.
1.5. Penghapusan polimorfik
Jika Anda mendesain hierarki kelas polimorfik yang instansnya dihapus menggunakan operator delete
, maka harus ada destruktor virtual terbuka di kelas dasar, ini memastikan bahwa destruktor tipe objek yang sebenarnya dipanggil ketika operator delete
diterapkan pada pointer ke kelas dasar. Jika aturan ini dilanggar, panggilan ke destruktor kelas dasar dapat terjadi, yang dapat menyebabkan kebocoran sumber daya.
1.6. Menghapus ketika deklarasi kelas tidak lengkap
Omnivora dari operator delete
dapat menciptakan masalah tertentu, dapat diterapkan pada pointer bertipe void*
atau ke pointer ke kelas yang memiliki deklarasi tidak lengkap (preemptive). Dalam hal ini, kesalahan tidak terjadi, hanya panggilan ke destruktor dilewati, hanya fungsi untuk membebaskan memori yang dipanggil. Pertimbangkan sebuah contoh:
class X;
Kode ini mengkompilasi bahkan jika deklarasi kelas X
penuh tidak tersedia di peer panggilan delete
. Benar, saat mengkompilasi (Visual Studio) peringatan dikeluarkan:
warning C4150: deletion of pointer to incomplete type 'X'; no destructor called
Jika ada implementasi X
dan CreateX()
, maka kode tersebut CreateX()
, jika CreateX()
mengembalikan pointer ke objek yang dibuat oleh operator new
, maka panggilan Foo()
berhasil dieksekusi, destructor tidak dipanggil. Jelas bahwa ini dapat menyebabkan sumber daya terkuras, jadi sekali lagi tentang perlunya berhati-hati tentang peringatan.
Situasi ini tidak terlalu mengada-ada, ia dapat dengan mudah muncul ketika menggunakan kelas-kelas seperti smart pointer atau kelas deskriptor. Scott Meyers menangani masalah ini di [Meyers3].
2. Murni fungsi virtual dan kelas abstrak
Konsep kelas antarmuka didasarkan pada konsep C ++ seperti fungsi virtual murni dan kelas abstrak.
2.1. Fungsi virtual murni
Fungsi virtual yang dideklarasikan menggunakan konstruk "=0"
disebut virtual murni.
class X {
Tidak seperti fungsi virtual biasa, fungsi virtual murni tidak dapat didefinisikan (dengan pengecualian destruktor, lihat bagian 2.3), tetapi harus didefinisikan ulang di salah satu kelas turunan.
Fungsi virtual murni dapat didefinisikan. Emblem Sutter menawarkan beberapa kegunaan yang bermanfaat untuk fitur ini [Bidik].
2.2. Kelas abstrak
Kelas abstrak adalah kelas yang memiliki setidaknya satu fungsi virtual murni. Kelas yang diturunkan dari kelas abstrak dan tidak menimpa setidaknya satu fungsi virtual murni juga akan abstrak. Standar C ++ melarang membuat instance dari kelas abstrak, Anda hanya dapat membuat instance turunan dari kelas non-abstrak. Dengan demikian, kelas abstrak dibuat untuk digunakan sebagai kelas dasar. Dengan demikian, jika konstruktor didefinisikan dalam kelas abstrak, maka tidak masuk akal untuk membuatnya terbuka, itu harus dilindungi.
2.3. Destructor virtual murni
Dalam beberapa kasus, disarankan untuk membuat destruktor virtual murni. Tetapi solusi ini memiliki dua fitur.
- Destructor murni virtual harus didefinisikan. (Definisi default biasanya digunakan, yaitu, menggunakan konstruksi
"=default"
.) Destructor kelas turunan memanggil destruktor kelas dasar di sepanjang seluruh rantai pewarisan dan, oleh karena itu, antrian dijamin untuk mencapai root - sebuah destruktor murni virtual. - Jika programmer belum mendefinisikan ulang destruktor virtual murni di kelas turunan, kompiler akan melakukannya untuknya (lihat bagian 1.1). Dengan demikian, kelas yang berasal dari kelas abstrak dengan destruktor virtual murni dapat kehilangan abstraknya tanpa secara eksplisit menimpa destruktor.
Contoh menggunakan destruktor virtual murni dapat ditemukan di bagian 4.4.
3. Kelas antarmuka
Kelas antarmuka adalah kelas abstrak yang tidak memiliki data dan sebagian besar terdiri dari fungsi virtual murni. Kelas semacam itu dapat memiliki fungsi virtual biasa (bukan murni virtual), misalnya, sebuah destruktor. Mungkin juga ada fungsi anggota statis, seperti fungsi pabrik.
3.1. Implementasi
Implementasi kelas antarmuka akan disebut kelas turunan di mana fungsi virtual murni didefinisikan ulang. Mungkin ada beberapa implementasi dari kelas antarmuka yang sama, dan dua skema dimungkinkan: horisontal, ketika beberapa kelas berbeda mewarisi kelas antarmuka yang sama, dan vertikal, ketika kelas antarmuka adalah akar dari hierarki polimorfik. Tentu saja, mungkin ada hibrida.
Poin kunci dari konsep kelas antarmuka adalah pemisahan lengkap antarmuka dari implementasi - klien hanya bekerja dengan kelas antarmuka, implementasi tidak tersedia untuk itu.
3.2. Penciptaan Obyek
Tidak dapat diaksesnya kelas implementasi menyebabkan masalah tertentu saat membuat objek. Klien harus membuat turunan dari kelas implementasi dan mendapatkan pointer ke kelas antarmuka di mana objek akan diakses. Karena kelas implementasi tidak tersedia, Anda tidak dapat menggunakan konstruktor, oleh karena itu, fungsi pabrik digunakan, yang didefinisikan pada sisi implementasi. Fungsi ini biasanya membuat objek menggunakan operator new
dan mengembalikan pointer ke objek yang dibuat, dilemparkan ke pointer ke kelas antarmuka. Fungsi pabrik dapat menjadi anggota statis dari kelas antarmuka, tetapi ini tidak perlu, misalnya, dapat menjadi anggota kelas pabrik khusus (yang, pada gilirannya, dapat dengan sendirinya menjadi kelas antarmuka) atau fungsi bebas. Fungsi pabrik tidak dapat mengembalikan pointer mentah ke kelas antarmuka, tetapi yang cerdas. Opsi ini dibahas di bagian 3.3.4 dan 4.3.2.
3.3. Hapus objek
Menghapus objek adalah operasi yang sangat kritis. Kesalahan menghasilkan kebocoran memori atau penghapusan ganda, yang biasanya menyebabkan crash program. Di bawah masalah ini dianggap sedetail mungkin, dengan banyak perhatian diberikan pada pencegahan tindakan pelanggan yang salah.
Ada empat opsi utama:
- Menggunakan operator
delete
. - Menggunakan fungsi virtual khusus.
- Menggunakan fungsi eksternal.
- Hapus otomatis menggunakan smart pointer.
3.3.1. Menggunakan operator delete
Untuk melakukan ini, Anda harus memiliki destruktor virtual terbuka di kelas antarmuka. Dalam hal ini, operator delete
, menyerukan pointer ke kelas antarmuka di sisi klien, menyediakan panggilan ke destruktor dari kelas implementasi. Opsi ini mungkin berfungsi, tetapi sulit untuk mengenalinya sebagai berhasil. Kami mendapat panggilan dari operator new
dan delete
di sisi yang berbeda dari "penghalang", new
di sisi implementasi, delete
di sisi klien. Dan jika implementasi kelas antarmuka dilakukan dalam modul terpisah (yang merupakan hal yang cukup umum), maka kita mendapatkan pelanggaran aturan dari bagian 1.4.
3.3.2. Menggunakan fungsi virtual khusus
Lebih progresif adalah pilihan lain: kelas antarmuka harus memiliki fungsi virtual khusus yang menghapus objek. Fungsi seperti itu, pada akhirnya, turun ke memanggil delete this
, tetapi ini sudah terjadi di sisi implementasi. Fungsi seperti itu dapat dipanggil dengan berbagai cara, misalnya, Delete()
, tetapi opsi lain juga digunakan: Release()
, Destroy()
, Dispose()
, Free()
, Close()
, Close()
, dll. Selain mengikuti aturan di bagian 1.4, opsi ini memiliki beberapa keunggulan tambahan.
- Memungkinkan Anda menggunakan fungsi alokasi / deallokasi memori yang ditentukan pengguna untuk kelas implementasi.
- Memungkinkan Anda menerapkan skema yang lebih kompleks untuk mengontrol masa pakai objek implementasi, misalnya, menggunakan penghitung referensi.
Dalam perwujudan ini, upaya untuk menghapus objek menggunakan operator delete
dapat dikompilasi dan bahkan dilakukan, tetapi ini adalah kesalahan. Untuk mencegahnya di kelas antarmuka, cukup memiliki destructor yang kosong atau murni virtual (lihat bagian 1.3). Perhatikan bahwa penggunaan operator delete
bisa sangat tertutup, misalnya, pointer cerdas standar menggunakan operator hapus untuk menghapus objek secara default dan kode yang sesuai sangat terkubur dalam implementasinya. Destructor yang dilindungi memungkinkan Anda untuk mendeteksi semua upaya tersebut pada tahap kompilasi.
3.3.3. Menggunakan fungsi eksternal
Opsi ini dapat menarik simetri tertentu prosedur untuk membuat dan menghapus objek, tetapi pada kenyataannya tidak memiliki kelebihan dibandingkan versi sebelumnya, tetapi ada banyak masalah tambahan. Opsi ini tidak direkomendasikan untuk digunakan dan tidak dipertimbangkan di masa mendatang.
3.3.4. Hapus otomatis menggunakan smart pointer
Dalam hal ini, fungsi pabrik tidak mengembalikan pointer mentah ke kelas antarmuka, tetapi pointer pintar yang sesuai. Pointer pintar ini dibuat di sisi implementasi dan merangkum objek penghapusan, yang secara otomatis menghapus objek implementasi ketika pointer pintar (atau salinan terakhirnya) keluar dari ruang lingkup di sisi klien. Dalam hal ini, fungsi virtual khusus untuk menghapus objek implementasi mungkin tidak diperlukan, tetapi destruktor yang dilindungi masih diperlukan, perlu untuk mencegah penggunaan yang salah dari operator delete
. (Benar, harus dicatat bahwa kemungkinan kesalahan seperti itu terasa berkurang.) Pilihan ini dibahas lebih rinci dalam Bagian 4.3.2.
3.4. Opsi lain untuk mengelola masa instance dari kelas implementasi
Dalam beberapa kasus, klien dapat menerima pointer ke kelas antarmuka, tetapi tidak memilikinya. Manajemen seumur hidup objek implementasi sepenuhnya di sisi implementasi. Sebagai contoh, suatu objek dapat menjadi objek tunggal statis (solusi ini khas untuk pabrik). Contoh lain terkait dengan interaksi dua arah, lihat bagian 3.7. Klien tidak boleh menghapus objek seperti itu, tetapi destruktor yang dilindungi untuk kelas antarmuka seperti itu diperlukan, perlu untuk mencegah penggunaan yang salah dari operator delete
.
3.5. Salin semantik
Untuk kelas antarmuka, membuat salinan objek implementasi menggunakan copy constructor tidak dimungkinkan, jadi jika menyalin diperlukan, maka kelas harus memiliki fungsi virtual yang membuat salinan objek implementasi dan mengembalikan pointer ke kelas antarmuka. Fungsi seperti ini sering disebut konstruktor virtual, dan nama tradisionalnya adalah Clone()
atau Duplicate()
.
Menggunakan operator penugasan salinan tidak dilarang, tetapi tidak dapat dianggap sebagai ide yang baik. Operator penugasan salinan selalu dipasangkan, harus dipasangkan dengan konstruktor salin. Operator yang dihasilkan oleh kompiler default tidak ada artinya, ia tidak melakukan apa-apa. Secara teoritis, Anda dapat mendeklarasikan operator penugasan murni virtual, diikuti dengan mengesampingkan, tetapi penugasan virtual bukan praktik yang disarankan; rincian dapat ditemukan di [Meyers1]. Selain itu, penugasan terlihat sangat tidak wajar: akses ke objek dari kelas implementasi biasanya dilakukan melalui pointer ke kelas antarmuka, sehingga penugasan akan terlihat seperti ini:
* = *;
Operator penugasan sebaiknya dilarang, dan jika perlu, semantik tersebut memiliki fungsi virtual yang sesuai di kelas antarmuka.
Ada dua cara untuk melarang penugasan.
- Nyatakan operator tugas yang dihapus (
=delete
). Jika kelas antarmuka membentuk hierarki, maka ini cukup untuk dilakukan di kelas dasar. Kerugian dari metode ini adalah bahwa hal itu mempengaruhi kelas implementasi, larangan juga berlaku untuk itu. - Nyatakan pernyataan tugas yang dilindungi dengan definisi default (
=default
). Ini tidak mempengaruhi kelas implementasi, tetapi dalam kasus hirarki kelas antarmuka, pengumuman seperti itu harus dibuat di setiap kelas.
3.6. Konstruktor kelas antarmuka
Seringkali, konstruktor dari kelas antarmuka tidak dideklarasikan. Dalam hal ini, kompiler menghasilkan konstruktor default yang diperlukan untuk mengimplementasikan warisan (lihat bagian 1.1). Konstruktor ini terbuka, meskipun cukup aman. Jika dalam kelas antarmuka konstruktor penyalinan dinyatakan dihapus ( =delete
), maka pembangkitan oleh kompilator konstruktor ditekan secara default, dan konstruktor tersebut harus dinyatakan secara eksplisit. Wajar untuk membuatnya aman dengan definisi default ( =default
). Pada prinsipnya, deklarasi konstruktor yang dilindungi seperti itu selalu dapat dilakukan. Contohnya ada di bagian 4.4.
3.7. Interaksi dua arah
Kelas antarmuka nyaman untuk menggunakan komunikasi dua arah. Jika beberapa modul dapat diakses melalui kelas antarmuka, maka klien juga dapat membuat implementasi dari beberapa kelas antarmuka dan meneruskan pointer ke mereka dalam modul. Melalui petunjuk ini, modul dapat menerima layanan dari klien dan juga mengirimkan data atau pemberitahuan ke klien.
3.8. Pointer pintar
Karena akses ke objek dari kelas implementasi biasanya dilakukan melalui pointer, wajar untuk menggunakan pointer pintar untuk mengontrol masa hidup mereka. Tetapi harus diingat bahwa jika opsi kedua untuk menghapus objek digunakan, maka dengan smart pointer standar perlu untuk mentransfer pengguna deleter (tipe) atau instance dari tipe ini. Jika ini tidak dilakukan, maka penunjuk pintar akan menggunakan operator hapus untuk menghapus objek, dan kode tidak akan dikompilasi (terima kasih kepada destruktor yang dilindungi). Pointer pintar standar (termasuk penggunaan penghilang kustom) dibahas secara rinci dalam [Josuttis], [Meyers3]. Contoh menggunakan penghapus khusus dapat ditemukan di bagian 4.3.1.
, , , .
3.9. -
- const. , , -, .
3.10. COM-
COM- , , COM β , COM- , C, , . COM- C++ , COM.
3.11.
(API) (SDK). . -, -, . , (Windows DLL), : -. . , , . LoadLibrary()
, -, .
4.
4.1.
, .
class IBase { protected: virtual ~IBase() = default;
.
class IActivatable : public IBase { protected: ~IActivatable() = default;
, , . , IBase
. , (. 1.3). , .
4.2.
class Activator : private IActivatable {
, , , - , .
4.3.
4.3.1.
. - ( IBase
):
struct BaseDeleter { void operator()(IBase* p) const { p->Delete(); } };
std::unique_ptr<>
- :
template <class I> // I β IBase using UniquePtr = std::unique_ptr<I, BaseDeleter>;
, , - , UniquePtr
.
-:
template <class I> // I β - CreateInstance() UniquePtr<I> CreateInstance() { return UniquePtr<I>(I::CreateInstance()); }
:
template <class I> // I β IBase UniquePtr<I> ToPtr(I* p) { return UniquePtr<I>(p); }
std::shared_ptr<>
std::unique_ptr<>
, , std::shared_ptr<>
. Activator
.
auto un1 = CreateInstance<IActivatable>(); un1->Activate(true); auto un2 = ToPtr(IActivatable::CreateInstance()); un2->Activate(true); std::shared_ptr<IActivatable> sh = CreateInstance<IActivatable>(); sh->Activate(true);
( β -):
std::shared_ptr<IActivatable> sh2(IActivatable::CreateInstance());
std::make_shared<>()
, ( ).
: , . : , - . 4.4.
4.3.2.
. -. std::shared_ptr<>
, , ( ). std::shared_ptr<>
( ) - , delete
. std::shared_ptr<>
- ( ) - . .
#include <memory> class IActivatable; using ActPtr = std::shared_ptr<IActivatable>; // class IActivatable { protected: virtual ~IActivatable() = default; // IActivatable& operator=(const IActivatable&) = default; // public: virtual void Activate(bool activate) = 0; static ActPtr CreateInstance(); // - }; // class Activator : public IActivatable { // ... public: Activator(); // ~Activator(); // void Activate(bool activate) override; }; Activator::Activator() {/* ... */} Activator::~Activator() {/* ... */} void Activator::Activate(bool activate) {/* ... */} ActPtr IActivatable::CreateInstance() { return ActPtr(new Activator()); }
- std::make_shared<>()
:
ActPtr IActivatable::CreateInstance() { return std::make_shared<Activator>(); }
std::unique_ptr<>
, , - , .
4.4.
C# Java C++ «», . . IBase
.
class IBase { protected: IBase() = default; virtual ~IBase() = 0;
, Delete()
, .
IBase::~IBase() = default; void IBase::Delete() { delete this; }
IBase
. Delete()
, . - IBase
. Delete()
, - . Delete()
, . , 4.3.1.
5. ,
5.1
, , , , .
, , IException
Exception
.
class IException { friend class Exception; virtual IException* Clone() const = 0; virtual void Delete() = 0; protected: virtual ~IException() = default; public: virtual const char* What() const = 0; virtual int Code() const = 0; IException& operator=(const IException&) = delete; }; class Exception { IException* const m_Ptr; public: Exception(const char* what, int code); Exception(const Exception& src) : m_Ptr(src.m_Ptr->Clone()) {} ~Exception() { m_Ptr->Delete(); } const IException* Ptr() const { return m_Ptr; } };
Exception
, IException
. , throw
, . Exception
, . - , .
Exception
, , .
IException
:
class ExcImpl : IException { friend class Exception; const std::string m_What; const int m_Code; ExcImpl(const char* what, int code); ExcImpl(const ExcImpl&) = default; IException* Clone() const override; void Delete() override; protected: ~ExcImpl() = default; public: const char* What() const override; int Code() const override; }; ExcImpl::ExcImpl(const char* what, int code) : m_What(what), m_Code(code) {} IException* ExcImpl::Clone() const { return new ExcImpl(*this); } void ExcImpl::Delete() { delete this; } const char* ExcImpl::What() const { return m_What.c_str(); } int ExcImpl::Code() const { return m_Code; }
Exception
:
Exception::Exception(const char* what, int code) : m_Ptr(new ExcImpl(what, code)) {}
, β .NET β , β , C++/CLI. , , , C++/CLI.
5.2
- :
template <typename T> class ICollect { protected: virtual ~ICollect() = default; public: virtual ICollect<T>* Clone() const = 0; virtual void Delete() = 0; virtual bool IsEmpty() const = 0; virtual int GetCount() const = 0; virtual T& GetItem(int ind) = 0; virtual const T& GetItem(int ind) const = 0; ICollect<T>& operator=(const ICollect<T>&) = delete; };
, -, .
template <typename T> class ICollect; template <typename T> class Iterator; template <typename T> class Contain { typedef ICollect<T> CollType; CollType* m_Coll; public: typedef T value_type; Contain(CollType* coll); ~Contain();
. , . , , , , - begin()
end()
, . (. [Josuttis]), for
. . , , .
6. -
. -, . . , ++. , .NET, Java Pyton. . , , . .NET Framework C++/CLI C++. .
7.
-, .
.
delete
.- .
- .
.
, delete
. , .
- , . , , delete
.
.
, , , , .
[GoF]
Gamma E., Helm R., Johnson R., Vlissides J. Metode desain berorientasi objek. Pola desain: Per. dari bahasa inggris - St. Petersburg: Peter, 2001.
[Josuttis]
Josattis, Nikolai M. C ++ Perpustakaan Standar: Panduan Referensi, edisi kedua: Per. dari bahasa inggris - M.: LLC βSaya. Williams, 2014.
[Dewhurst]
Dewhurst, Stefan K. Slippery menempatkan C ++. Cara menghindari masalah saat merancang dan menyusun program Anda:: Per. dari bahasa inggris - M .: DMK Press, 2012.
[Meyers1]
, . C++. 35 .: . . β .: , 2000.
[Meyers2]
, . C++. 55 .: . . β .: , 2014.
[Meyers3]
, . C++: 42 C++11 C++14.: . . β .: Β«.. Β», 2016.
[Sutter]
, . C++.: . . β : Β«.. Β», 2015.
[Sutter/Alexandrescu]
, . , . ++.: . . β .: Β«.. Β», 2015.