Saya tidak mengerti mengapa saya melakukan ini:
struct S {
int a;
S(int aa) : a(aa) {}
S() = default;
};
Kenapa tidak bilang saja:
S() {} // instead of S() = default;
mengapa membawa sintaksis baru untuk itu?
Saya tidak mengerti mengapa saya melakukan ini:
struct S {
int a;
S(int aa) : a(aa) {}
S() = default;
};
Kenapa tidak bilang saja:
S() {} // instead of S() = default;
mengapa membawa sintaksis baru untuk itu?
Jawaban:
Konstruktor default default secara spesifik didefinisikan sebagai sama dengan konstruktor default yang ditentukan pengguna tanpa daftar inisialisasi dan pernyataan gabungan kosong.
§12.1 / 6 [class.ctor] Konstruktor default yang default dan tidak didefinisikan sebagai dihapus didefinisikan secara implisit ketika digunakan untuk membuat objek tipe kelasnya atau ketika secara eksplisit default setelah deklarasi pertama. Konstruktor default yang didefinisikan secara implisit melakukan serangkaian inisialisasi kelas yang akan dilakukan oleh konstruktor default yang ditulis pengguna untuk kelas tersebut tanpa ctor-initializer (12.6.2) dan pernyataan gabungan kosong. [...]
Namun, sementara kedua konstruktor akan berperilaku sama, memberikan implementasi kosong tidak memengaruhi beberapa properti kelas. Memberikan konstruktor yang ditentukan pengguna, meskipun tidak menghasilkan apa-apa, membuat tipe tersebut tidak agregat dan juga tidak sepele . Jika Anda ingin kelas Anda menjadi tipe agregat atau trivial (atau berdasarkan transitivitas, tipe POD), maka Anda perlu menggunakannya = default
.
§8.5.1 / 1 [dcl.init.aggr] Agregat adalah array atau kelas tanpa konstruktor yang disediakan pengguna, [dan ...]
§12.1 / 5 [class.ctor] Konstruktor default adalah sepele jika tidak disediakan oleh pengguna dan [...]
§9 / 6 [kelas] Kelas sepele adalah kelas yang memiliki konstruktor default sepele dan [...]
Untuk menunjukkan:
#include <type_traits>
struct X {
X() = default;
};
struct Y {
Y() { };
};
int main() {
static_assert(std::is_trivial<X>::value, "X should be trivial");
static_assert(std::is_pod<X>::value, "X should be POD");
static_assert(!std::is_trivial<Y>::value, "Y should not be trivial");
static_assert(!std::is_pod<Y>::value, "Y should not be POD");
}
Selain itu, secara eksplisit default konstruktor akan membuatnya constexpr
jika konstruktor implisit akan dan juga akan memberikan spesifikasi pengecualian yang sama dengan konstruktor implisit. Dalam kasus yang Anda berikan, konstruktor implisit tidak akan constexpr
(karena akan membuat anggota data tidak diinisialisasi) dan juga akan memiliki spesifikasi pengecualian kosong, jadi tidak ada perbedaan. Tapi ya, dalam kasus umum Anda bisa secara manual menentukan constexpr
dan spesifikasi pengecualian untuk mencocokkan konstruktor implisit.
Menggunakan = default
memang membawa keseragaman, karena juga dapat digunakan dengan menyalin / memindahkan konstruktor dan destruktor. Konstruktor salinan kosong, misalnya, tidak akan melakukan hal yang sama dengan konstruktor salin default (yang akan melakukan salinan anggota-anggotanya). Menggunakan sintaks = default
(atau = delete
) secara seragam untuk masing-masing fungsi anggota khusus ini membuat kode Anda lebih mudah dibaca dengan secara eksplisit menyatakan maksud Anda.
constexpr
konstruktor (7.1.5), konstruktor default yang didefinisikan secara implisit adalah constexpr
."
constexpr
jika deklarasi implisit akan, (b) secara implisit dianggap memiliki fungsi yang sama. pengecualian-spesifikasi seolah-olah telah dinyatakan secara implisit (15,4), ... "Tidak ada bedanya dalam kasus khusus ini, tetapi secara umum foo() = default;
memiliki sedikit kelebihan foo() {}
.
constexpr
(karena anggota data dibiarkan tidak diinisialisasi) dan spesifikasi pengecualiannya memungkinkan semua pengecualian. Saya akan membuatnya lebih jelas.
constexpr
(yang Anda sebutkan seharusnya tidak membuat perbedaan di sini): struct S1 { int m; S1() {} S1(int m) : m(m) {} }; struct S2 { int m; S2() = default; S2(int m) : m(m) {} }; constexpr S1 s1 {}; constexpr S2 s2 {};
Hanya s1
memberikan kesalahan, bukan s2
. Baik dentang dan g ++.
Saya punya contoh yang akan menunjukkan perbedaan:
#include <iostream>
using namespace std;
class A
{
public:
int x;
A(){}
};
class B
{
public:
int x;
B()=default;
};
int main()
{
int x = 5;
new(&x)A(); // Call for empty constructor, which does nothing
cout << x << endl;
new(&x)B; // Call for default constructor
cout << x << endl;
new(&x)B(); // Call for default constructor + Value initialization
cout << x << endl;
return 0;
}
Keluaran:
5
5
0
Seperti yang kita lihat panggilan untuk konstruktor A () kosong tidak menginisialisasi anggota, sementara B () melakukannya.
n2210 memberikan beberapa alasan:
Manajemen default memiliki beberapa masalah:
- Definisi konstruktor digabungkan; mendeklarasikan konstruktor apa pun akan menekan konstruktor default.
- Default destructor tidak sesuai untuk kelas polimorfik, membutuhkan definisi eksplisit.
- Setelah default ditekan, tidak ada cara untuk menghidupkannya kembali.
- Implementasi default seringkali lebih efisien daripada implementasi yang ditentukan secara manual.
- Implementasi non-default adalah non-sepele, yang mempengaruhi semantik tipe, misalnya membuat tipe non-POD.
- Tidak ada cara untuk melarang fungsi anggota khusus atau operator global tanpa menyatakan pengganti (non-sepele).
type::type() = default; type::type() { x = 3; }
Dalam beberapa kasus, badan kelas dapat berubah tanpa memerlukan perubahan dalam definisi fungsi anggota karena perubahan standar dengan deklarasi anggota tambahan.
Lihat Rule-of-Three menjadi Rule-of-Five dengan C ++ 11? :
Perhatikan bahwa memindahkan konstruktor dan memindahkan operator penugasan tidak akan dihasilkan untuk kelas yang secara eksplisit menyatakan salah satu fungsi anggota khusus lainnya, yang menyalin konstruktor dan menyalin operator tidak akan dihasilkan untuk kelas yang secara eksplisit menyatakan memindahkan konstruktor atau memindahkan operator penugasan, dan bahwa kelas dengan destruktor yang dinyatakan secara eksplisit dan konstruktor salinan yang ditentukan secara implisit atau operator penugasan salinan yang didefinisikan secara implisit dianggap usang
= default
secara umum, bukan alasan untuk melakukan = default
konstruktor vs melakukan { }
.
{}
sudah menjadi ciri bahasa sebelum pengenalan =default
, alasan-alasan ini tidak secara implisit bergantung pada perbedaan (misalnya "tidak ada cara untuk membangkitkan [default ditekan]" menyiratkan bahwa {}
adalah tidak setara dengan default ).
Ini masalah semantik dalam beberapa kasus. Tidak terlalu jelas dengan konstruktor default, tetapi menjadi jelas dengan fungsi anggota yang dihasilkan kompiler lainnya.
Untuk konstruktor default, dimungkinkan untuk membuat konstruktor default apa pun dengan badan kosong dianggap sebagai kandidat untuk menjadi konstruktor sepele, sama seperti menggunakan =default
. Bagaimanapun, konstruktor default kosong yang lama adalah legal C ++ .
struct S {
int a;
S() {} // legal C++
};
Apakah kompiler memahami konstruktor ini sepele atau tidak relevan dalam banyak kasus di luar optimasi (manual atau kompiler).
Namun, upaya ini untuk memperlakukan badan fungsi kosong sebagai "default" rusak sepenuhnya untuk jenis fungsi anggota lainnya. Pertimbangkan pembuat salinan:
struct S {
int a;
S() {}
S(const S&) {} // legal, but semantically wrong
};
Dalam kasus di atas, pembuat salinan yang ditulis dengan badan kosong sekarang salah . Sebenarnya tidak lagi menyalin apa pun. Ini adalah serangkaian semantik yang sangat berbeda dari semantik konstruktor salin standar. Perilaku yang diinginkan mengharuskan Anda untuk menulis beberapa kode:
struct S {
int a;
S() {}
S(const S& src) : a(src.a) {} // fixed
};
Meskipun dengan kasus sederhana ini, bagaimanapun, menjadi lebih banyak beban bagi kompiler untuk memverifikasi bahwa copy constructor identik dengan yang akan dihasilkan sendiri atau untuk itu untuk melihat bahwa copy constructor itu sepele (setara dengan memcpy
, pada dasarnya ). Kompiler harus memeriksa setiap ekspresi penginisialisasi anggota dan memastikannya identik dengan ekspresi untuk mengakses anggota yang sesuai sumber dan tidak ada yang lain, pastikan tidak ada anggota yang tersisa dengan konstruksi default non-sepele, dll. Ini mundur dengan cara proses kompiler akan gunakan untuk memverifikasi bahwa versi yang dihasilkan sendiri dari fungsi ini sepele.
Pertimbangkan operator penugasan salinan yang bisa lebih adil, terutama dalam kasus non-sepele. Ini adalah satu ton boiler-plate yang Anda tidak ingin harus menulis untuk banyak kelas tetapi Anda tetap harus melakukannya di C ++ 03:
struct T {
std::shared_ptr<int> b;
T(); // the usual definitions
T(const T&);
T& operator=(const T& src) {
if (this != &src) // not actually needed for this simple example
b = src.b; // non-trivial operation
return *this;
};
Itu adalah kasus sederhana, tetapi sudah lebih dari kode yang Anda ingin dipaksa untuk menulis untuk tipe sederhana seperti itu T
(terutama sekali kita melemparkan operasi ke dalam campuran). Kita tidak dapat mengandalkan tubuh kosong yang berarti "isi default" karena tubuh kosong sudah benar-benar valid dan memiliki makna yang jelas. Bahkan, jika badan kosong digunakan untuk menunjukkan "isi default" maka tidak akan ada cara untuk secara eksplisit membuat konstruktor no-op copy atau sejenisnya.
Lagi-lagi masalah konsistensi. Tubuh kosong berarti "tidak melakukan apa-apa" tetapi untuk hal-hal seperti copy constructor Anda benar-benar tidak ingin "tidak melakukan apa-apa" melainkan "melakukan semua hal yang biasanya Anda lakukan jika tidak ditekan." Oleh karena itu =default
. Ini diperlukan untuk mengatasi fungsi anggota yang dihasilkan kompiler yang ditekan seperti menyalin / memindahkan konstruktor dan operator penugasan. Maka hanya "jelas" untuk membuatnya bekerja untuk konstruktor default juga.
Mungkin menyenangkan untuk membuat konstruktor default dengan benda kosong dan konstruktor anggota / basis yang sepele juga dianggap sepele seperti jika mereka ingin =default
membuat kode lama menjadi lebih optimal dalam beberapa kasus, tetapi sebagian besar kode tingkat rendah bergantung pada hal sepele konstruktor default untuk optimisasi juga bergantung pada konstruktor salinan sepele. Jika Anda harus pergi dan "memperbaiki" semua konstruktor salinan lama Anda, itu benar-benar tidak banyak keharusan untuk memperbaiki semua konstruktor default lama Anda. Ini juga jauh lebih jelas dan lebih jelas menggunakan eksplisit =default
untuk menunjukkan niat Anda.
Ada beberapa hal lain yang akan dilakukan oleh fungsi anggota yang dikompilasi oleh kompiler yaitu Anda harus secara eksplisit membuat perubahan untuk mendukung. Mendukung constexpr
konstruktor default adalah salah satu contohnya. Hanya lebih mudah digunakan secara mental =default
daripada harus menandai fungsi dengan semua kata kunci khusus lainnya dan yang tersirat oleh =default
dan itu adalah salah satu tema C ++ 11: membuat bahasa lebih mudah. Masih ada banyak kutil dan kompromi back-compat, tetapi jelas bahwa ini adalah langkah besar ke depan dari C ++ 03 ketika datang untuk kemudahan penggunaan.
= default
akan terjadi a=0;
dan tidak! Saya harus menjatuhkannya demi : a(0)
. Saya masih bingung tentang seberapa bermanfaatkah = default
ini, apakah ini tentang kinerja? apakah itu akan pecah di suatu tempat jika saya tidak menggunakannya = default
? Saya mencoba membaca semua jawaban di sini, membeli, saya baru mengenal beberapa hal c ++ dan saya mengalami banyak kesulitan memahaminya.
a=0
Contoh Anda adalah karena perilaku tipe sepele, yang merupakan topik yang terpisah (meskipun terkait).
= default
dan masih a
akan memberikan =0
? dalam beberapa cara? Anda pikir saya bisa membuat pertanyaan baru seperti "bagaimana memiliki konstruktor = default
dan memberikan bidang akan diinisialisasi dengan benar?", btw saya punya masalah dalam struct
dan tidak class
, dan aplikasi berjalan dengan benar bahkan tidak menggunakan = default
, saya bisa tambahkan minimum struct pada pertanyaan itu jika itu bagus :)
struct { int a = 0; };
Jika Anda kemudian memutuskan Anda membutuhkan konstruktor, Anda dapat mengaturnya secara default, tetapi perhatikan bahwa jenisnya tidak akan sepele (yang tidak masalah).
Karena penghentian std::is_pod
dan alternatifnya std::is_trivial && std::is_standard_layout
, cuplikan dari jawaban @JosephMansfield menjadi:
#include <type_traits>
struct X {
X() = default;
};
struct Y {
Y() {}
};
int main() {
static_assert(std::is_trivial_v<X>, "X should be trivial");
static_assert(std::is_standard_layout_v<X>, "X should be standard layout");
static_assert(!std::is_trivial_v<Y>, "Y should not be trivial");
static_assert(std::is_standard_layout_v<Y>, "Y should be standard layout");
}
Perhatikan bahwa Y
tata letak masih standar.
default
bukan kata kunci baru, itu hanya penggunaan baru kata kunci yang sudah dipesan.