inisialisasi struct/kelas tanpa konstruktor di stack vs heap

Saya ingin mengetahui aturan untuk menghilangkan struct (atau kelas) yang tidak memiliki konstruktor default di C++.

Secara khusus, tampaknya jika disimpan dalam tumpukan (misalnya, sebagai variabel lokal) variabel tersebut tidak diinisialisasi, tetapi jika dialokasikan di heap, variabel tersebut diinisialisasi nol (diuji dengan GCC 4.9.1). Apakah ini dijamin portabel?

Contoh program:

#include <iostream>
#include <map>
using namespace std;

struct X {
    int i, j, k;
    void show() { cout << i << " " << j << " " << k << endl; }
};

int fib(int i) {
    return (i > 1) ? fib(i-1) + fib(i-2) : 1;
}

int main() {
    map<int, X> m;            
    fib(10);                  // fills the stack with cruft
    X x1;                     // local
    X &x2 = m[1];             // heap-allocated within map
    X *x3 = new X();          // explicitly heap-allocated
    x1.show();  // --> outputs whatever was on the heap in those positions
    x2.show();  // --> outputs 0 0 0 
    x3->show(); // --> outputs 0 0 0     
    return 0;
}

Diedit: menghapus "atau haruskah saya menggunakan konstruktor" di bagian yang dicetak tebal; karena yang membuat saya bertanya adalah saya ingin tahu apakah ini merupakan perilaku yang dijamin atau tidak - kita semua setuju bahwa kode yang dapat dibaca lebih baik dengan konstruktor eksplisit.


person tucuxi    schedule 06.05.2015    source sumber
comment
Saya tidak cukup tahu untuk memposting jawaban yang lengkap, tetapi new X() akan menginisialisasi X, sedangkan new X tidak (dalam kasus ini di mana X merupakan agregat)   -  person Quentin    schedule 06.05.2015
comment
Anda harus selalu menulis konstruktor dan mengatur variabel internal suatu kelas. Terlepas apakah kompiler sudah menyetelnya.   -  person maja    schedule 06.05.2015
comment
@Quentin Kedua pernyataan ini sama, tetapi yang kedua (new X) harus diutamakan   -  person maja    schedule 06.05.2015
comment
@maja keduanya tidak sama dan saya lebih suka new X(). Mengapa Anda lebih memilih new X?   -  person TartanLlama    schedule 06.05.2015
comment
@maja mereka tidak sama. Lihat di sini, di sini dan di sini. Dalam hal ini X diinisialisasi nilai (dinolkan), atau tidak ada yang dilakukan (konstruktor default sepele). Bagaimanapun, aturan inisialisasi adalah mimpi buruk, jadi gunakan saja konstruktor eksplisit dan selesaikan...   -  person Quentin    schedule 06.05.2015
comment
@TartanLlama Karena Anda bisa mendapatkan masalah dalam situasi di mana ada fungsi yang disebut X(). Dalam hal ini, kompiler akan menafsirkan X() sebagai pemanggilan fungsi, sedangkan new X selalu tidak ambigu.   -  person maja    schedule 06.05.2015
comment
Salah orang, Anda menjawab @TartanLlama ;)   -  person Quentin    schedule 06.05.2015
comment
@Quentin: Lalu serahkan pada seseorang yang melakukan cukup tahu! Tidak perlu menjawab/beranggapan di komentar; bukan itu tujuan mereka. Bersulang   -  person Lightness Races in Orbit    schedule 06.05.2015
comment
@maja: Benar-benar omong kosong. Menghapus () tidak menyelamatkan Anda dari ambiguitas itu!   -  person Lightness Races in Orbit    schedule 06.05.2015
comment
@Quentin Apakah kita membicarakan hal yang berbeda? Kode A* a = new A(); setara dengan A* a = new A; bukan? Jika tidak ada konstruktor, yang default akan digunakan - tidak masalah apakah Anda menghilangkan tanda kurung atau tidak.   -  person maja    schedule 06.05.2015
comment
@maja: Apa yang kamu bicarakan? Itu bukan parse ambiguitas dalam C++.   -  person Puppy    schedule 06.05.2015
comment
@maja: Tidak. Tidak, keduanya tidak sama. Oleh karena itu pertanyaan dan jawabannya.   -  person Puppy    schedule 06.05.2015
comment
@maja: Tidak, ini tidak setara. Seperti yang telah beberapa kali dijelaskan oleh beberapa orang.   -  person Lightness Races in Orbit    schedule 06.05.2015
comment
@LightnessRacesinOrbit Jika ada fungsi dengan nama itu, kompiler akan dibatalkan karena kesalahan   -  person maja    schedule 06.05.2015
comment
@maja: Klik tautan yang saya berikan kepada Anda. Kemudian baca teksnya. Anda menegaskan bahwa menghapus () entah bagaimana menghindari ambiguitas; Saya menunjukkan, dengan contoh nyata, bahwa hal itu tidak terjadi.   -  person Lightness Races in Orbit    schedule 06.05.2015
comment
@LightnessRacesinOrbit Saya tahu apa yang ada di komentar saya, dan tidak lebih;)   -  person Quentin    schedule 06.05.2015
comment
@LightnessRacesinOrbit Hm... Saya perlu memeriksanya ketika saya di rumah... sepertinya saya mencampuradukkan sesuatu...   -  person maja    schedule 06.05.2015


Jawaban (3)


Bukan alokasi dinamis yang tidak menginisialisasi struct anggota Anda; itu sintaks Anda:

X* ptr = new X();
//            ^^
// 
// as opposed to just:
X* ptr = new X;

Kalau mau terjamin, teruslah menulis itu. :)

Alternatifnya, yang cocok dengan durasi penyimpanan otomatis yang setara, adalah dengan menggunakan sintaks {} yang lebih baru:

X* ptr = new X{};   // dynamic
X  obj{};           // automatic

Dan objek dengan durasi penyimpanan statis selalu diinisialisasi terlebih dahulu, apa pun yang terjadi, jadi Anda tercakup di sana secara default.

person Lightness Races in Orbit    schedule 06.05.2015
comment
Jawaban bagus; Saya tidak pernah berpikir untuk menelepon baru tanpa tanda kurung untuk membandingkan. Adakah ide tentang mengapa std::map () menginisialisasi struct saya? - person tucuxi; 06.05.2015
comment
@tucuxi: Karena itulah fungsinya! [C++11: 23.4.4.3/1]: Efek: Jika tidak ada kunci yang setara dengan x di peta, masukkan value_type(x, T()) ke dalam peta. Standar bisa saja menghindari inisialisasi nilai ini dan menggunakan inisialisasi default, namun hal ini akan menambah kompleksitas dan mungkin mengurangi kegunaan. Maksud saya, dalam banyak kasus, lebih berguna untuk memiliki nilai yang dapat dibaca dengan aman daripada menghindari satu kali penulisan yang mungkin menjadi mubazir. - person Lightness Races in Orbit; 06.05.2015
comment
@tucuxi: Karena tidak melakukan hal itu bodoh. Anda hanya akan mendapatkan sekumpulan nilai-nilai tidak berharga yang harus Anda tetapkan setelahnya. - person Puppy; 06.05.2015
comment
@tucuxi Saya tidak dapat menemukan kata yang pasti tentang ini [masukkan doa guru Standar di sini], tetapi IINM hanya mengambil referensi ke variabel yang tidak diinisialisasi adalah UB, dan std::map harus mengembalikan referensi tersebut. - person Quentin; 06.05.2015
comment
@Quentin: Saya belum pernah mendengarnya dan saya tidak dapat menemukan bukti apa pun tentang hal itu pada pemindaian teks standar [sangat] cepat. Bukan berarti hal itu akan sangat mengejutkanku. - person Lightness Races in Orbit; 06.05.2015

Selalu gunakan konstruktor jika Anda ingin memberikan nilai tertentu kepada anggota kelas Anda. Itulah gunanya konstruktor. Satu-satunya saat Anda tidak perlu menulis konstruktor yang menetapkan nilai yang Anda inginkan adalah jika bahasa sudah menyediakannya untuk Anda seperti copy konstruktor. Konstruktor default tidak ada dalam daftar ini untuk int dan tipe serupa lainnya sehingga Anda harus menambahkan konstruktor ke tipe Anda sendiri yang menyetelnya dengan tepat.

person Puppy    schedule 06.05.2015
comment
Ini sepertinya nasihat yang berat tanpa kualifikasi. Menambahkan konstruktor yang ditentukan pengguna akan mengubah properti tipe tersebut dengan cara yang mungkin tidak Anda inginkan dalam beberapa situasi. - person Lightness Races in Orbit; 06.05.2015
comment
maaf, saya telah mengedit atau haruskah saya menggunakan konstruktor saja, karena saya sangat ingin tahu mengapa hal-hal terjadi seperti itu. Ya, saya setuju bahwa kode tersebut akan lebih baik jika menggunakan konstruktor, tetapi saya ingin memahami mengapa kode tersebut berfungsi seperti ini sekarang - person tucuxi; 06.05.2015
comment
@Lightness: Siapa saja yang benar-benar mengalami salah satu situasi tersebut dan memahami properti apa yang diubah dan mengapa tidak memerlukan saran dari jawaban ini untuk mengetahui apa yang harus dilakukan. Tidak menulis konstruktor saat Anda membutuhkannya karena alasan konyol jauh lebih umum dan jauh lebih buruk daripada secara tidak sengaja membuat tipe Anda non-POD atau semacamnya. - person Puppy; 06.05.2015
comment
Ah, begitu: jawaban ini sengaja menghilangkan detail penting karena siapa pun yang membacanya tidak akan memahami detail tersebut. Keangkuhan akademis yang terbaik! - person Lightness Races in Orbit; 06.05.2015

Selama Anda tidak memiliki konstruktor apa pun, semua anggota bersifat publik, dan tidak ada warisan yang terlibat, struct/kelas Anda kemungkinan besar merupakan agregat.

Untuk mendapatkan inisialisasi nol portabel, cukup gunakan X x = {}; yang melakukan inisialisasi nol.

Berikut kutipan standar tentang agregat (8.5.1):

Agregat adalah larik atau kelas (Klausul 9) tanpa konstruktor yang disediakan pengguna (12.1), tanpa inisialisasi kurung kurawal atau sama dengan untuk anggota data non-statis (9.2), tanpa anggota data non-statis pribadi atau terlindungi ( Klausul 11), tidak ada kelas dasar (Klausul 10), dan tidak ada fungsi virtual (10.3).

referensi: Mengapa saya tidak bisa menguatkan inisialisasi sebuah struct yang diturunkan dari struct lain?

person timato    schedule 06.05.2015