Kami melanjutkan seri "C ++, menggali lebih dalam." Tujuan dari seri ini adalah untuk memberi tahu sebanyak mungkin tentang berbagai fitur bahasa, mungkin cukup istimewa. Artikel ini membahas tentang kelebihan operator new/delete
. Ini adalah artikel ketiga dalam seri ini, yang pertama didedikasikan untuk fungsi dan templat yang berlebihan, terletak di sini , yang kedua didedikasikan untuk operator yang kelebihan beban, yang terletak di sini . Artikel ini menyimpulkan seri tiga artikel tentang kelebihan muatan di C ++.
Daftar Isi
1. Bentuk standar dari operator baru / hapus
C ++ mendukung beberapa opsi operator new/delete
. Mereka dapat dibagi menjadi standar dasar, standar tambahan dan kebiasaan. Bagian ini dan bagian 2 membahas formulir standar, formulir kustom akan dibahas di bagian 3.1.1. Bentuk standar dasar
Bentuk standar utama dari operator yang new/delete
digunakan saat membuat dan menghapus objek dan array jenis adalah T
sebagai berikut:new T()
new T[]
delete ptr;
delete[] ptr;
Pekerjaan mereka dapat digambarkan sebagai berikut. Ketika operator dipanggil new
, memori pertama dialokasikan ke objek. Jika seleksi berhasil, konstruktor dipanggil. Jika konstruktor melempar pengecualian, maka memori yang dialokasikan dibebaskan. Ketika operator dipanggil, delete
semuanya terjadi dalam urutan terbalik: pertama, destructor dipanggil, kemudian memori dibebaskan. Destruktor seharusnya tidak melempar pengecualian.Ketika operatornew[]
digunakan untuk membuat berbagai objek, memori pertama dialokasikan untuk seluruh array. Jika seleksi berhasil, maka konstruktor default (atau konstruktor lain, jika ada penginisialisasi) 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[]
, dan untuk semua elemen array, destructor dipanggil dengan urutan terbalik dari konstruktor, kemudian memori yang dialokasikan dibebaskan.Perhatian! Penting untuk memanggil bentuk operator yang benardelete
tergantung pada apakah satu objek atau array dihapus. Aturan ini harus dipatuhi dengan ketat, jika tidak Anda bisa mendapatkan perilaku tidak terdefinisi, yaitu, apa pun bisa terjadi: kebocoran memori, kerusakan, dll. Lihat [Meyers1] untuk detailnya.Dalam uraian di atas, satu klarifikasi diperlukan. Untuk apa yang disebut tipe sepele (tipe bawaan, struktur gaya C), konstruktor default mungkin tidak dipanggil, dan destruktor tidak melakukan apa pun dalam hal apa pun.Fungsi alokasi memori standar, ketika tidak mungkin untuk memenuhi permintaan, melempar pengecualian tipe std::bad_alloc
. Tetapi pengecualian ini dapat ditangkap, untuk ini Anda perlu menginstal pencegat global menggunakan panggilan fungsi set_new_handler()
, untuk lebih jelasnya lihat [Meyers1].Semua bentuk operatordelete
terapkan dengan aman ke null pointer.Saat membuat array dengan operator, new[]
ukurannya dapat diatur ke nol.Kedua bentuk operator new
memungkinkan penggunaan inisialisasi dalam kawat gigi.new int{42}
new int[8]{1,2,3,4}
1.2. Formulir standar tambahan
Saat menghubungkan file header <new>
, 4 formulir operator standar tersedia new
:new(ptr) T();
new(ptr) T[];
new(std::nothrow) T();
new(std::nothrow) T[];
Dua yang pertama disebut new
penempatan non-alokasi new
. Argumen ptr
adalah penunjuk ke wilayah memori yang cukup besar untuk menampung instance atau array. Selain itu, area memori harus memiliki perataan yang sesuai. Versi operator new
ini tidak mengalokasikan memori, melainkan hanya memberikan panggilan ke konstruktor. Dengan demikian, opsi ini memungkinkan Anda untuk memisahkan fase alokasi memori dan inisialisasi objek. Fitur ini secara aktif digunakan dalam wadah standar. Operator delete
untuk objek yang dibuat dengan cara ini tentu saja tidak dapat dipanggil. Untuk menghapus objek, Anda harus langsung memanggil destruktor, dan kemudian membebaskan memori dengan cara yang tergantung pada metode alokasi memori.Dua opsi kedua disebut operator tidak melempar pengecualian new
(nothrow new
) dan berbeda dalam hal jika tidak mungkin memenuhi permintaan, mereka kembali nullptr
, tetapi tidak melempar pengecualian tipe std::bad_alloc
. Menghapus objek terjadi menggunakan operator utama delete
. Opsi ini dianggap usang dan tidak disarankan untuk digunakan.1.3. Alokasi Memori dan Fungsi Gratis
Bentuk standar operator new/delete
menggunakan fungsi alokasi dan deallokasi berikut:void* operator new(std::size_t size);
void operator delete(void* ptr);
void* operator new[](std::size_t size);
void operator delete[](void* ptr);
void* operator new(std::size_t size, void* ptr);
void* operator new[](std::size_t size, void* ptr);
void* operator new(std::size_t size, const std::nothrow_t& nth);
void* operator new[](std::size_t size, const std::nothrow_t& nth);
Fungsi-fungsi ini didefinisikan dalam namespace global. Fungsi alokasi memori untuk pernyataan host new
tidak melakukan apa pun dan mengembalikannya ptr
.C ++ 17 mendukung bentuk alokasi memori dan fungsi deallokasi tambahan, yang menunjukkan penyelarasan. Inilah beberapa di antaranya:void* operator new (std::size_t size, std::align_val_t al);
void* operator new[](std::size_t size, std::align_val_t al);
Formulir ini tidak secara langsung dapat diakses oleh pengguna, mereka digunakan oleh kompiler untuk objek yang persyaratan penyelarasannya lebih unggul __STDCPP_DEFAULT_NEW_ALIGNMENT__
, sehingga masalah utama adalah bahwa pengguna tidak sengaja menyembunyikannya (lihat bagian 2.2.1). Ingatlah bahwa dalam C ++ 11 menjadi mungkin untuk secara eksplisit mengatur penyelarasan tipe pengguna.struct alignas(32) X { };
2. Kelebihan bentuk standar operator baru / hapus
Overloading bentuk standar operator new/delete
terdiri dari mendefinisikan fungsi yang ditentukan pengguna untuk mengalokasikan dan membebaskan memori yang tandatangannya sesuai dengan yang standar. Fungsi-fungsi ini dapat didefinisikan dalam namespace global atau dalam kelas, tetapi tidak dalam namespace selain global. Fungsi alokasi memori untuk pernyataan host standar new
tidak dapat didefinisikan dalam namespace global. Setelah definisi seperti itu, operator yang sesuai new/delete
akan menggunakannya, bukan yang standar.2.1. Kelebihan dalam namespace global
Misalkan, misalnya, dalam sebuah modul dalam ruang nama global yang fungsi-fungsi yang ditentukan pengguna didefinisikan:void* operator new(std::size_t size)
{
}
void operator delete(void* ptr)
{
}
Dalam hal ini, sebenarnya akan ada penggantian (penggantian) fungsi standar untuk mengalokasikan dan membebaskan memori untuk semua panggilan operator new/delete
untuk setiap kelas (termasuk yang standar) di seluruh modul. Ini dapat menyebabkan kekacauan total. Perhatikan bahwa mekanisme substitusi yang dijelaskan adalah mekanisme khusus yang diterapkan hanya untuk kasus ini, dan bukan mekanisme C ++ umum. Dalam hal ini, ketika mengimplementasikan fungsi pengguna untuk mengalokasikan dan membebaskan memori, menjadi tidak mungkin untuk memanggil fungsi standar yang sesuai, mereka sepenuhnya tersembunyi (operator ::
tidak membantu), dan ketika Anda mencoba memanggilnya, panggilan rekursif ke fungsi pengguna terjadi.Fungsi Ditentukan Namespace Globalvoid* operator new(std::size_t size, const std::nothrow_t& nth)
{
}
Ini juga akan menggantikan yang standar, tetapi akan ada lebih sedikit potensi masalah, karena operator yang tidak melempar pengecualian new
jarang digunakan. Tetapi formulir standar juga tidak tersedia.Situasi yang sama dengan fungsi untuk array.Pernyataan berlebihan new/delete
di namespace global sangat tidak disarankan.2.2. Kelas terlalu banyak
Operator kelebihan muatan new/delete
di kelas tidak memiliki kelemahan yang dijelaskan di atas. Overloading hanya efektif ketika membuat dan menghapus instance dari kelas yang sesuai, terlepas dari konteks memohon operator new/delete
. Saat menerapkan fungsi yang ditentukan pengguna untuk mengalokasikan dan membebaskan memori menggunakan operator, ::
Anda dapat mengakses fungsi standar yang sesuai. Pertimbangkan sebuah contoh.class X
{
public:
void* operator new(std::size_t size)
{
std::cout << "X new\n";
return ::operator new(size);
}
void operator delete(void* ptr)
{
std::cout << "X delete\n";
::operator delete(ptr);
}
void* operator new[](std::size_t size)
{
std::cout << "X new[]\n";
return ::operator new[](size);
}
void operator delete[](void* ptr)
{
std::cout << "X delete[]\n";
::operator delete[](ptr);
}
};
Dalam contoh ini, pelacakan hanya ditambahkan ke operasi standar. Sekarang, dalam hal new X()
dan new X[N]
akan menggunakan fungsi-fungsi ini untuk mengalokasikan dan membebaskan memori.Fungsi-fungsi ini secara formal statis dan dapat dinyatakan sebagai static
. Tetapi pada dasarnya mereka adalah instance, dengan pemanggilan fungsi operator new()
, penciptaan instance dimulai, dan pemanggilan fungsi operator delete()
menyelesaikan penghapusannya. Fungsi-fungsi ini tidak pernah dipanggil untuk tugas lain. Selain itu, seperti yang akan ditunjukkan di bawah, fungsi operator delete()
ini pada dasarnya virtual. Jadi lebih tepat untuk mendeklarasikannya tanpa static
.2.2.1. Akses ke bentuk standar dari operator baru / hapus
Operator new/delete
dapat digunakan dengan operator resolusi ruang lingkup tambahan, misalnya ::new(p) X()
. Dalam hal ini, fungsi yang operator new()
didefinisikan di kelas akan diabaikan, dan standar yang sesuai akan digunakan. Dengan cara yang sama, Anda dapat menggunakan operator delete
.2.2.2. Menyembunyikan bentuk lain dari operator baru / hapus
Jika sekarang untuk kelas X
kami mencoba menggunakan melempar atau tidak melempar pengecualian new
, kami mendapatkan kesalahan. Faktanya adalah bahwa fungsi operator new(std::size_t size)
akan menyembunyikan bentuk lain operator new()
. Masalahnya bisa diselesaikan dengan dua cara. Pada yang pertama, Anda perlu menambahkan opsi yang sesuai ke kelas (opsi ini seharusnya hanya mendelegasikan operasi fungsi standar). Dalam yang kedua, Anda perlu menggunakan operator new
dengan operator resolusi lingkup, misalnya ::new(p) X()
.2.2.3. Kontainer standar
Jika kita mencoba menempatkan instance X
dalam beberapa wadah standar, misalnya std::vector<X>
, kita akan melihat bahwa fungsi kita tidak digunakan untuk mengalokasikan dan mengosongkan memori. Faktanya adalah bahwa semua kontainer standar memiliki mekanisme sendiri untuk mengalokasikan dan membebaskan memori (kelas pengalokasi khusus, yang merupakan parameter templat dari wadah), dan mereka menggunakan operator penempatan untuk menginisialisasi elemen new
.2.2.4. Warisan
Fungsi untuk mengalokasikan dan membebaskan memori diwarisi. Jika fungsi-fungsi ini didefinisikan di kelas dasar, tetapi tidak di yang diturunkan, maka operator akan kelebihan beban untuk kelas turunan new/delete
dan fungsi-fungsi yang didefinisikan dan dialokasikan di kelas dasar akan digunakan untuk mengalokasikan dan membebaskan memori.Sekarang pertimbangkan hirarki kelas polimorfik, di mana setiap kelas membebani operator new/delete
. Sekarang, biarkan instance dari kelas turunan dihapus menggunakan operator delete
melalui pointer ke kelas dasar. Jika destruktor dari kelas dasar adalah virtual, maka standar menjamin bahwa destruktor dari kelas turunan ini dipanggil. Dalam hal ini operator delete()
, pemanggilan fungsi yang ditentukan untuk kelas turunan ini juga dijamin . Jadi, fungsinya operator delete()
sebenarnya virtual.2.2.5. Bentuk alternatif fungsi delete () operator
Di kelas (terutama ketika warisan digunakan), kadang-kadang nyaman untuk menggunakan bentuk alternatif fungsi untuk membebaskan memori:void operator delete(void* p, std::size_t size);
void operator delete[](void* p, std::size_t size);
Parameter size
menentukan ukuran elemen (bahkan dalam versi untuk array). Formulir ini memungkinkan Anda untuk menggunakan berbagai fungsi untuk mengalokasikan dan mengosongkan memori, tergantung pada kelas turunan tertentu.3. Operator pengguna baru / hapus
C ++ dapat mendukung bentuk operator kustom dari new
formulir berikut:new() T()
new() T[]
Agar bentuk-bentuk ini didukung, perlu untuk menentukan fungsi yang sesuai untuk mengalokasikan dan membebaskan memori:void* operator new(std::size_t size, );
void* operator new[](std::size_t size, );
void operator delete(void* p, );
void operator delete[](void* p, );
Daftar parameter tambahan fungsi alokasi memori tidak boleh kosong dan tidak boleh terdiri dari satu void*
atau const std::nothrow_t&
, yaitu, tanda tangannya tidak boleh bertepatan dengan salah satu yang standar. Daftar parameter tambahan di operator new()
dan operator delete()
harus cocok. Argumen yang diteruskan ke operator new
harus sesuai dengan parameter tambahan fungsi alokasi memori. Fungsi kustom operator delete()
juga bisa dalam bentuk dengan parameter ukuran opsional.Fungsi-fungsi ini dapat didefinisikan di namespace global atau di kelas, tetapi tidak di namespace selain global. Jika mereka didefinisikan dalam namespace global, mereka tidak menggantikan, tetapi overload, fungsi standar mengalokasikan dan membebaskan memori, sehingga penggunaannya dapat diprediksi dan aman, dan fungsi standar selalu tersedia. Jika mereka didefinisikan di kelas, mereka menyembunyikan formulir standar, tetapi akses ke formulir standar dapat diperoleh dengan menggunakan operator ::
, ini dijelaskan secara rinci dalam bagian 2.2. Formuliroperator new
yang new
ditentukan pengguna disebut penempatan yang ditentukan pengguna new
. Mereka tidak boleh bingung dengan operator penempatan standar (tidak mengalokasikan) yang new
dijelaskan dalam bagian 1.2.Bentuk operator yang sesuai delete
tidak ada. Ada new
dua cara untuk menghapus objek yang dibuat menggunakan operator yang ditentukan pengguna . Jika fungsi yang ditentukan pengguna operator new()
mendelegasikan operasi alokasi memori ke fungsi alokasi memori standar, maka operator standar dapat digunakan delete
. Jika tidak, Anda harus memanggil destruktor secara eksplisit, dan kemudian fungsi yang ditentukan pengguna operator delete()
. Kompilator memanggil fungsi yang ditentukan pengguna operator delete()
hanya dalam satu kasus: ketika new
konstruktor melempar pengecualian selama operasi operator yang ditentukan pengguna .Berikut ini sebuah contoh (dalam lingkup global).void* operator new(std::size_t size, int a, const char* b)
{
std::cout << "new " << a << " + " << b << "\n";
return ::operator new(size);
}
void operator delete(void* p, int a, const char* b)
{
std::cout << "delete " << a << " + " << b << "\n";
::operator delete(p);
}
class X {};
X* p = new(42, "meow") X();
delete p;
4. Definisi fungsi alokasi memori
Dalam contoh ini, fungsi pengguna operator new()
dan operator delete()
operasi yang didelegasikan sesuai dengan fungsi standar. Terkadang opsi ini berguna, tetapi tujuan utama overloading new/delete
adalah menciptakan mekanisme baru untuk mengalokasikan / membebaskan memori. Tugas ini tidak sederhana, dan sebelum melakukannya, seseorang harus dengan cermat memikirkan semuanya. Scott Meyers [Meyers1] membahas kemungkinan motif untuk membuat keputusan semacam itu (tentu saja, yang utama adalah efisiensi). Dia juga membahas masalah teknis utama yang terkait dengan implementasi yang benar dari fungsi yang ditentukan pengguna untuk mengalokasikan dan membebaskan memori (menggunakan fungsiset_new_handler()
, sinkronisasi multi-utas, penyelarasan). Guntheroth memberikan contoh implementasi fungsi alokasi dan deallokasi memori yang didefinisikan pengguna yang relatif sederhana. Sebelum membuat versi Anda sendiri, Anda harus mencari solusi yang sudah jadi, sebagai contoh, Anda bisa membawa perpustakaan Pool dari proyek Boost.5. Kelas allocator dari kontainer standar
Seperti disebutkan di atas, kontainer standar menggunakan kelas pengalokasi khusus untuk mengalokasikan dan membebaskan memori. Kelas-kelas ini adalah templat parameter wadah dan pengguna dapat menentukan versinya untuk kelas tersebut. Motif untuk solusi semacam itu kira-kira sama dengan untuk operator kelebihan muatan new/delete
. [Guntheroth] menjelaskan cara membuat kelas seperti itu.Bibliografi
[Guntheroth]Gunteroth, Kurt. Optimalisasi program dalam C ++. Metode yang terbukti untuk meningkatkan produktivitas: Per. dari bahasa Inggris - SPb.: Alpha-book LLC, 2017.[Meyers1]Meyers, Scott. Penggunaan C ++ secara efektif. 55 cara pasti untuk meningkatkan struktur dan kode program Anda:: Per. dari bahasa Inggris - M .: DMK Press, 2014.