Bagaimana “= default” berbeda dari “{}” untuk konstruktor dan destruktor default?

169

Saya awalnya memposting ini sebagai pertanyaan hanya tentang destruktor, tapi sekarang saya menambahkan pertimbangan konstruktor default. Inilah pertanyaan aslinya:

Jika saya ingin memberi kelas saya destruktor yang virtual, tetapi sebaliknya sama dengan apa yang akan dihasilkan oleh kompiler, saya dapat menggunakan =default:

class Widget {
public:
   virtual ~Widget() = default;
};

Tapi sepertinya saya bisa mendapatkan efek yang sama dengan kurang mengetik menggunakan definisi kosong:

class Widget {
public:
   virtual ~Widget() {}
};

Apakah ada cara di mana kedua definisi ini berperilaku berbeda?

Berdasarkan balasan yang diposting untuk pertanyaan ini, situasi untuk konstruktor default tampak serupa. Mengingat hampir tidak ada perbedaan makna antara " =default" dan " {}" untuk destruktor, apakah hampir sama tidak ada perbedaan makna antara opsi-opsi ini untuk konstruktor default? Yaitu, dengan anggapan saya ingin membuat jenis di mana objek jenis itu akan dibuat dan dihancurkan, mengapa saya ingin mengatakan

Widget() = default;

dari pada

Widget() {}

?

Saya minta maaf jika memperpanjang pertanyaan ini setelah posting aslinya melanggar beberapa aturan SO. Posting pertanyaan yang hampir identik untuk konstruktor default mengejutkan saya sebagai pilihan yang kurang diinginkan.

KnowItAllWannabe
sumber
1
Bukan yang saya tahu, tetapi = defaultlebih eksplisit, dan konsisten dengan dukungan untuk itu dengan konstruktor.
chris
11
Saya tidak tahu pasti, tapi saya pikir yang pertama sesuai dengan definisi "trivial destructor", sedangkan yang terakhir tidak. Begitu std::has_trivial_destructor<Widget>::valuejuga trueuntuk yang pertama, tetapi falseuntuk yang kedua. Apa implikasinya itu, saya juga tidak tahu. :)
GManNickG
10
Destructor virtual tidak pernah sepele.
Luc Danton
@LucDanton: Saya kira membuka mata saya dan melihat kode akan bekerja juga! Terima kasih sudah mengoreksi.
GManNickG
Terkait: stackoverflow.com/questions/20828907/…
Gabriel Staples

Jawaban:

103

Ini adalah pertanyaan yang sama sekali berbeda ketika bertanya tentang konstruktor daripada destruktor.

Jika destruktor Anda virtual, maka perbedaannya dapat diabaikan, seperti yang ditunjukkan Howard . Namun, jika destruktor Anda non-virtual , itu cerita yang sama sekali berbeda. Hal yang sama berlaku untuk konstruktor.

Menggunakan = defaultsintaks untuk fungsi anggota khusus (konstruktor default, menyalin / memindahkan konstruktor / tugas, destruktor dll) berarti sesuatu yang sangat berbeda dari hanya melakukan {}. Dengan yang terakhir, fungsi menjadi "yang disediakan pengguna". Dan itu mengubah segalanya.

Ini adalah kelas sepele menurut definisi C ++ 11:

struct Trivial
{
  int foo;
};

Jika Anda mencoba untuk membangunnya secara default, kompiler akan membuat konstruktor default secara otomatis. Hal yang sama berlaku untuk menyalin / memindahkan dan merusak. Karena pengguna tidak menyediakan fungsi-fungsi anggota ini, spesifikasi C ++ 11 menganggap ini sebagai kelas "sepele". Oleh karena itu sah untuk melakukan ini, seperti memcpy isinya sekitar untuk menginisialisasi mereka dan sebagainya.

Ini:

struct NotTrivial
{
  int foo;

  NotTrivial() {}
};

Seperti namanya, ini tidak lagi sepele. Ini memiliki konstruktor default yang disediakan pengguna. Tidak masalah jika itu kosong; sejauh menyangkut aturan C ++ 11, ini bukan tipe yang sepele.

Ini:

struct Trivial2
{
  int foo;

  Trivial2() = default;
};

Sekali lagi seperti namanya, ini adalah jenis yang sepele. Mengapa? Karena Anda mengatakan kepada kompiler untuk secara otomatis menghasilkan konstruktor default. Karena itu konstruktor tidak "disediakan pengguna." Dan oleh karena itu, jenisnya dianggap sepele, karena tidak memiliki konstruktor default yang disediakan pengguna.

The = defaultsintaks terutama ada untuk melakukan hal-hal seperti konstruktor copy / tugas, ketika Anda menambahkan fungsi anggota yang mencegah penciptaan fungsi tersebut. Tapi itu juga memicu perilaku khusus dari kompiler, jadi itu berguna dalam konstruktor default / destruktor juga.

Nicol Bolas
sumber
2
Jadi masalah utama tampaknya adalah apakah kelas yang dihasilkan sepele, dan yang mendasari masalah itu adalah perbedaan antara fungsi khusus yang dideklarasikan pengguna (yang merupakan kasus untuk =defaultfungsi) dan fungsi yang disediakan pengguna (yang merupakan kasus untuk {}). Fungsi yang dideklarasikan oleh pengguna dan yang disediakan pengguna dapat mencegah pembuatan fungsi anggota khusus lainnya (misalnya, destruktor yang dideklarasikan pengguna mencegah pembuatan operasi pemindahan), tetapi hanya fungsi khusus yang disediakan pengguna yang membuat kelas menjadi non-sepele. Baik?
KnowItAllWannabe
@KnowItAllWannabe: Itu ide umum, ya.
Nicol Bolas
Saya memilih ini sebagai jawaban yang diterima, hanya karena itu mencakup konstruktor dan (dengan mengacu pada jawaban Howard) destruktor.
KnowItAllWannabe
Tampaknya ada kata yang hilang di sini "sejauh aturan C ++ 11 yang bersangkutan, Anda hak-hak tipe yang sepele" Saya akan memperbaikinya tapi saya tidak yakin 100% yakin apa yang dimaksudkan.
jcoder
2
= defaulttampaknya berguna untuk memaksa kompiler untuk menghasilkan konstruktor default meskipun ada konstruktor lain; konstruktor default tidak dinyatakan secara implisit jika ada konstruktor yang dideklarasikan pengguna lain disediakan.
bgfvdu3w
42

Keduanya non-sepele.

Mereka berdua memiliki spesifikasi noexcept yang sama tergantung pada spesifikasi noexcept dari pangkalan dan anggota.

Satu-satunya perbedaan yang saya deteksi sejauh ini adalah jika Widgetmengandung basis atau anggota dengan destruktor yang tidak dapat diakses atau dihapus:

struct A
{
private:
    ~A();
};

class Widget {
    A a_;
public:
#if 1
   virtual ~Widget() = default;
#else
   virtual ~Widget() {}
#endif
};

Maka =defaultsolusinya akan dikompilasi, tetapi Widgettidak akan menjadi tipe yang dapat dirusak. Yaitu jika Anda mencoba untuk merusak Widget, Anda akan mendapatkan kesalahan waktu kompilasi. Tetapi jika tidak, Anda punya program kerja.

Otoh, jika Anda memasok destruktor yang disediakan pengguna , maka hal-hal tidak akan mengkompilasi apakah Anda merusak Widget:

test.cpp:8:7: error: field of type 'A' has private destructor
    A a_;
      ^
test.cpp:4:5: note: declared private here
    ~A();
    ^
1 error generated.
Howard Hinnant
sumber
9
Menarik: dengan kata lain, dengan =default;kompiler tidak akan menghasilkan destruktor kecuali jika digunakan, dan karenanya tidak akan memicu kesalahan. Ini tampak aneh bagi saya, bahkan jika belum tentu bug. Saya tidak bisa membayangkan perilaku ini diamanatkan dalam standar.
Nik Bougalis
"Maka solusi = default akan mengkompilasi" Tidak itu tidak akan. Baru saja diuji dalam vs
nano
Apa pesan kesalahannya, dan versi VS apa?
Howard Hinnant
35

Perbedaan penting antara

class B {
    public:
    B(){}
    int i;
    int j;
};

dan

class B {
    public:
    B() = default;
    int i;
    int j;
};

adalah bahwa konstruktor default yang didefinisikan dengan B() = default;dianggap tidak didefinisikan pengguna . Ini berarti bahwa dalam hal inisialisasi nilai seperti pada

B* pb = new B();  // use of () triggers value-initialization

inisialisasi khusus yang tidak menggunakan konstruktor sama sekali akan terjadi dan untuk tipe bawaan ini akan menghasilkan inisialisasi nol . Dalam hal B(){}ini tidak akan terjadi. Standar C ++ n3337 § 8.5 / 7 mengatakan

Menginisialisasi nilai objek bertipe T berarti:

- jika T adalah tipe kelas (mungkin berkualifikasi cv) (Klausul 9) dengan konstruktor yang disediakan pengguna (12.1), maka konstruktor default untuk T disebut (dan inisialisasi tidak terbentuk jika T tidak memiliki konstruktor default yang dapat diakses. );

- jika T adalah tipe kelas non-union (mungkin memenuhi syarat cv) tanpa konstruktor yang disediakan pengguna , maka objek tersebut nol-diinisialisasi dan, jika konstruktor default yang dinyatakan secara implisit T adalah non-sepele, maka konstruktor itu disebut.

- jika T adalah tipe array, maka setiap elemen diinisialisasi-nilai; - jika tidak, objek diinisialisasi nol.

Sebagai contoh:

#include <iostream>

class A {
    public:
    A(){}
    int i;
    int j;
};

class B {
    public:
    B() = default;
    int i;
    int j;
};

int main()
{
    for( int i = 0; i < 100; ++i) {
        A* pa = new A();
        B* pb = new B();
        std::cout << pa->i << "," << pa->j << std::endl;
        std::cout << pb->i << "," << pb->j << std::endl;
        delete pa;
        delete pb;
    }
  return 0;
}

hasil yang mungkin:

0,0
0,0
145084416,0
0,0
145084432,0
0,0
145084416,0
//...

http://ideone.com/k8mBrd

4pie0
sumber
Lalu, mengapa "{}" dan "= default" selalu menginisialisasi std :: string ideone.com/LMv5Uf ?
nawfel bgh
1
@nawfelbgh Konstruktor default A () {} memanggil konstruktor default untuk std :: string karena ini bukan tipe POD. Ctor default std :: string menginisialisasi menjadi kosong, 0 string ukuran. Ctoral default untuk skalar tidak melakukan apa-apa: objek dengan durasi penyimpanan otomatis (dan sub-objeknya) diinisialisasi untuk nilai yang tidak ditentukan.
4