Aturan 3 ( aturan 5 dalam standar c ++ baru) menyatakan:
Jika Anda perlu secara eksplisit mendeklarasikan baik destruktor, copy constructor atau operator penugasan sendiri, Anda mungkin perlu secara eksplisit mendeklarasikan ketiganya.
Tetapi, di sisi lain, " Kode Bersih " Martin menyarankan untuk menghapus semua konstruktor dan penghancur kosong (halaman 293, G12: Berantakan ):
Apa gunanya konstruktor default tanpa implementasi? Yang harus dilakukan hanyalah mengacaukan kode dengan artefak yang tidak berarti.
Jadi, bagaimana menangani dua pendapat yang berlawanan ini? Haruskah konstruktor / penghancur kosong benar-benar dilaksanakan?
Contoh berikutnya menunjukkan dengan tepat apa yang saya maksud:
#include <iostream>
#include <memory>
struct A
{
A( const int value ) : v( new int( value ) ) {}
~A(){}
A( const A & other ) : v( new int( *other.v ) ) {}
A& operator=( const A & other )
{
v.reset( new int( *other.v ) );
return *this;
}
std::auto_ptr< int > v;
};
int main()
{
const A a( 55 );
std::cout<< "a value = " << *a.v << std::endl;
A b(a);
std::cout<< "b value = " << *b.v << std::endl;
const A c(11);
std::cout<< "c value = " << *c.v << std::endl;
b = c;
std::cout<< "b new value = " << *b.v << std::endl;
}
Kompilasi denda menggunakan g ++ 4.6.1 dengan:
g++ -std=c++0x -Wall -Wextra -pedantic example.cpp
Destructor untuk struct A
kosong, dan tidak benar-benar diperlukan. Jadi, haruskah ada di sana, atau haruskah dihapus?
sumber
virtual ~base () = default;
jangan mengompilasi (dengan alasan yang bagus)auto_ptr
keduanya.Jawaban:
Sebagai permulaan aturan mengatakan "mungkin", jadi itu tidak selalu berlaku.
Poin kedua yang saya lihat di sini adalah jika Anda harus mendeklarasikan salah satu dari ketiganya, itu karena melakukan sesuatu yang istimewa seperti mengalokasikan memori. Dalam hal ini, yang lain tidak akan kosong karena mereka harus menangani tugas yang sama (seperti menyalin konten memori yang dialokasikan secara dinamis di copy constructor atau membebaskan memori tersebut).
Jadi sebagai kesimpulan, Anda tidak boleh mendeklarasikan konstruktor kosong atau destruktor, tetapi sangat mungkin bahwa jika diperlukan, yang lain juga diperlukan.
Sebagai contoh Anda: Dalam kasus seperti itu, Anda dapat membiarkan destruktor keluar. Jelas tidak melakukan apa-apa. Penggunaan pointer cerdas adalah contoh sempurna di mana dan mengapa aturan 3 tidak berlaku.
Ini hanya panduan untuk melihat kembali kode Anda jika Anda lupa mengimplementasikan fungsionalitas penting yang mungkin terlewatkan.
sumber
Sebenarnya tidak ada kontradiksi di sini. Aturan 3 berbicara tentang destructor, copy constructor dan operator penugasan copy. Paman Bob berbicara tentang konstruktor default kosong.
Jika Anda membutuhkan destruktor, maka kelas Anda mungkin berisi pointer ke memori yang dialokasikan secara dinamis, dan Anda mungkin ingin memiliki copy ctor dan
operator=()
yang melakukan copy yang dalam. Ini sepenuhnya ortogonal apakah Anda memerlukan konstruktor default atau tidak.Perhatikan juga bahwa di C ++ ada situasi ketika Anda membutuhkan konstruktor default, bahkan jika itu kosong. Katakanlah kelas Anda memiliki konstruktor non-default. Dalam hal ini, kompiler tidak akan menghasilkan konstruktor default untuk Anda. Itu berarti bahwa objek-objek dari kelas ini tidak dapat disimpan dalam wadah STL, karena wadah-wadah itu mengharapkan objek-objek dapat dibangun secara default.
Di sisi lain, jika Anda tidak berencana untuk memasukkan objek kelas Anda ke dalam wadah STL, konstruktor default kosong tentu saja adalah kekacauan yang tidak berguna.
sumber
Di sini potensi Anda (*) setara dengan satu konstruktor / tugas / destruktor default memiliki tujuan: mendokumentasikan fakta yang Anda miliki tentang masalah dan menentukan bahwa perilaku default sudah benar. BTW, dalam C ++ 11, hal-hal belum cukup stabil untuk mengetahui apakah
=default
dapat melayani tujuan itu.(Ada tujuan potensial lain: memberikan definisi out of line alih-alih inline default, lebih baik untuk mendokumentasikan secara eksplisit jika Anda memiliki alasan untuk melakukannya).
(*) Berpotensi karena saya tidak ingat kasus kehidupan nyata di mana aturan tiga tidak berlaku, jika saya harus melakukan sesuatu dalam satu, saya harus melakukan sesuatu dalam yang lain.
Edit setelah Anda menambahkan contoh. contoh Anda menggunakan auto_ptr menarik. Anda menggunakan penunjuk cerdas, tetapi bukan penunjuk yang tepat untuk pekerjaan itu. Saya lebih suka menulis yang - terutama jika situasinya sering terjadi - daripada melakukan apa yang Anda lakukan. (Jika saya tidak salah, standar atau penambah tidak menyediakannya).
sumber
Aturan 5 adalah perpanjangan secara hati-hati dari aturan 3 yang merupakan perilaku berhati-hati terhadap kemungkinan penyalahgunaan objek.
Jika Anda perlu memiliki destruktor, itu berarti Anda melakukan beberapa "manajemen sumber daya" selain default (hanya membangun dan merusak nilai-nilai ).
Karena menyalin, menetapkan, memindahkan dan mentransfer dengan nilai salin standar , jika Anda tidak memegang nilai - nilai adil , Anda harus menentukan apa yang harus dilakukan.
Yang mengatakan, C ++ menghapus salinan jika Anda menentukan langkah dan menghapus langkah jika Anda menentukan salinan. Dalam sebagian besar kasus, Anda harus menentukan apakah Anda ingin meniru nilai (karena itu salin mut klon sumber daya, dan pindahkan tidak masuk akal) atau manajer sumber daya (dan karenanya pindahkan sumber daya, di mana salinan tidak memiliki arti: aturan dari 3 menjadi aturan 3 lainnya )
Kasus-kasus ketika Anda harus mendefinisikan kedua salinan dan pindah (aturan 5) cukup jarang: biasanya Anda memiliki "nilai besar" yang harus disalin jika diberikan ke objek yang berbeda, tetapi dapat dipindahkan jika diambil dari objek sementara (menghindari sebuah clone kemudian menghancurkan ). Itu terjadi untuk wadah STL atau wadah aritmatika.
Sebuah case dapat berupa matriks: mereka harus mendukung salinan karena mereka adalah nilai, (
a=b; c=b; a*=2; b*=3;
tidak boleh saling mempengaruhi) tetapi mereka dapat dioptimalkan dengan mendukung juga memindahkan (a = 3*b+4*c
memiliki+
yang membutuhkan dua temporer dan menghasilkan sementara: menghindari kloning dan menghapus dapat berguna)sumber
Saya lebih suka ungkapan yang berbeda dari aturan tiga, yang tampaknya lebih masuk akal, yaitu "jika kelas Anda membutuhkan destruktor (selain destruktor virtual kosong) mungkin juga memerlukan copy constructor dan operator penugasan."
Menentukannya sebagai hubungan satu arah dari destruktor membuat beberapa hal menjadi lebih jelas:
Itu tidak berlaku dalam kasus di mana Anda menyediakan pembangun salinan atau operator penugasan non-standar sebagai pengoptimalan saja.
Alasan aturan ini adalah bahwa konstruktor atau operator penugasan salinan default dapat mengacaukan manajemen sumber daya manual. Jika Anda mengelola sumber daya secara manual, Anda mungkin menyadari bahwa Anda akan membutuhkan destruktor untuk melepaskannya.
sumber
Ada hal lain yang tidak disebutkan dalam diskusi: Seorang destruktor harus selalu virtual.
Konstruktor perlu dinyatakan sebagai virtual di kelas dasar untuk menjadikannya virtual di semua kelas turunan juga. Jadi, bahkan jika kelas dasar Anda tidak memerlukan destruktor, Anda akhirnya mendeklarasikan dan mengimplementasikan destruktor kosong.
Jika Anda memakai semua peringatan di (-Wall -Wextra -Weffc ++) g ++ akan memperingatkan Anda tentang hal ini. Saya menganggap itu praktik yang baik untuk selalu mendeklarasikan destruktor virtual di kelas mana pun, karena Anda tidak pernah tahu, apakah kelas Anda akhirnya akan menjadi kelas dasar. Jika penghancur virtual tidak diperlukan, tidak ada salahnya. Jika ya, Anda menghemat waktu untuk menemukan kesalahan.
sumber