Aturan 5 - untuk menggunakannya atau tidak?


20

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 Akosong, dan tidak benar-benar diperlukan. Jadi, haruskah ada di sana, atau haruskah dihapus?


15
2 kutipan berbicara tentang berbagai hal. Atau saya benar-benar kehilangan maksud Anda.
Benjamin Bannier

1
@honk Dalam standar pengkodean tim saya, kami memiliki aturan untuk selalu menyatakan semua 4 (konstruktor, destruktor, salin konstruktor). Saya bertanya-tanya apakah itu benar-benar masuk akal untuk dilakukan. Apakah saya benar-benar harus selalu mendeklarasikan destruktor, walaupun mereka kosong?
BЈовић

Adapun desctructors kosong pikirkan tentang ini: codesynthesis.com/~boris/blog/2012/04/04/… . Kalau tidak, aturan 3 (5) masuk akal bagi saya, tidak tahu mengapa seseorang menginginkan aturan 4.
Benjamin Bannier

@honk Hati-hati dengan informasi yang Anda temukan di internet. Tidak semuanya benar. Misalnya, virtual ~base () = default;jangan mengompilasi (dengan alasan yang bagus)
BЈовић

@ VJovic, Tidak, Anda tidak harus mendeklarasikan destruktor kosong, kecuali Anda harus membuatnya virtual. Dan sementara kita membahasnya, Anda seharusnya tidak menggunakan auto_ptrkeduanya.
Dima

Jawaban:


44

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.


Dengan menggunakan smart pointer, destruktor kosong dalam banyak kasus (saya katakan> 99% destruktor dalam basis kode saya kosong, karena hampir setiap kelas menggunakan idiom jerawat).
BЈовић

Wow, itu sangat banyak jerawat. Saya menyebutnya bau. Dengan banyak kompiler berjerawat akan lebih sulit untuk dioptimalkan (misalnya lebih sulit untuk inline).
Benjamin Bannier

@honk Apa yang Anda maksud dengan "banyak kompiler berjerawat"? :)
BЈовић

@VJovic: maaf, salah ketik: 'kode jerawat'
Benjamin Bannier

4

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.


2

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 =defaultdapat 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).


Contohnya menunjukkan poin saya. Destructor tidak benar-benar diperlukan, tetapi aturan 3 mengatakan harus ada di sana.
BЈовић

1

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*cmemiliki +yang membutuhkan dua temporer dan menghasilkan sementara: menghindari kloning dan menghapus dapat berguna)


1

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:

  1. Itu tidak berlaku dalam kasus di mana Anda menyediakan pembangun salinan atau operator penugasan non-standar sebagai pengoptimalan saja.

  2. 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.


-3

Ada hal lain yang tidak disebutkan dalam diskusi: Seorang destruktor harus selalu virtual.

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    virtual ~A(){}
    ...
}

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.


1
Tapi saya tidak ingin konstruktor virtual. Jika saya melakukan itu, maka setiap panggilan ke metode apa pun akan menggunakan pengiriman virtual. btw mencatat bahwa tidak ada yang namanya "virtual constructor" di c ++. Juga, saya mengkompilasi contoh sebagai tingkat peringatan yang sangat tinggi.
BЈовић

IIRC, aturan yang digunakan gcc untuk peringatannya, dan aturan yang biasanya saya ikuti, adalah bahwa harus ada destruktor virtual jika ada metode virtual lain di kelas.
Jules
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.