Konstruktor kosong default yang aneh pada perilaku warisan virtual di GCC
Saya memiliki situasi berikut dari kelas Berasal dengan warisan virtual ke kelas Basis dalam kode saya:
class Base {
int x;
public:
Base(int x): x{x} {}
virtual void f() = 0;
};
class Derived : public virtual Base {
public:
Derived() = default;
};
class Concrete: public Derived {
public:
Concrete(): Base{42} {}
void f() override {}
};
Tautan: https://godbolt.org/z/bn1EY6
GCC (trunk) memberikan kesalahan berikut: error: use of deleted function 'Derived::Derived()'
sementara Clang (trunk) mengkompilasinya tanpa masalah.
GCC berfungsi jika saya mengubah konstruktor menjadi Derived() {}
alih-alih Derived() = default
atau mendefinisikan konstruktor kosong pada kelas Base.
Mengapa = default
penghapusan fungsi di GCC dalam kasus ini?
Jawaban
Standar mengatakan (draf terbaru):
[class.default.ctor]
Konstruktor default default untuk kelas X didefinisikan sebagai dihapus jika:
- X adalah gabungan yang ... [[tidak berlaku]]
- X adalah kelas non-serikat yang memiliki anggota varian M dengan ... [[tidak berlaku]]
- setiap anggota data non-statis tanpa penginisialisasi anggota default ([class.mem]) adalah tipe referensi, [[tidak berlaku]]
- anggota data non-statis non-varian apa pun dari tipe yang memenuhi syarat const ... [[tidak berlaku]]
- X adalah gabungan dan ... [[tidak berlaku]]
- X adalah kelas non-serikat dan semua anggota dari setiap anggota serikat anonim ... [[tidak berlaku]]
- [berlaku jika basis adalah subobjek yang berpotensi dibangun ] setiap sub objek yang berpotensi dibangun, kecuali untuk anggota data non-statis dengan brace-or-equal-initializer, memiliki tipe kelas M (atau lariknya) dan salah satu M tidak memiliki konstruktor default atau overload resolusi ([over.match]) seperti yang diterapkan untuk menemukan hasil konstruktor M yang sesuai dalam ambiguitas atau dalam fungsi yang dihapus atau tidak dapat diakses dari konstruktor default default, atau
- setiap subobjek yang berpotensi dibangun memiliki tipe dengan destruktor yang dihapus atau tidak dapat diakses dari konstruktor default default. [[tidak berlaku]]
Hanya satu aturan yang berpotensi berlaku untuk konstruktor default default yang sedang dihapus, dan itu tergantung pada apakah basis adalah subobjek yang berpotensi dibangun .
[khusus]
Untuk kelas, anggota data non-statisnya, kelas basis langsung non-virtualnya, dan, jika kelas tersebut bukan abstrak ([class.abstract]), kelas basis virtualnya disebut subobjek yang berpotensi dibangun.
Derived
bersifat abstrak (karena tidak mengimplementasikan semua fungsi virtual murni), dan Base
merupakan basis virtual, oleh karena itu basis tersebut bukanlah subobjek yang berpotensi dibangun, dan oleh karena itu satu-satunya aturan yang seharusnya diterapkan untuk konstruktor default yang dihapus tidak berlaku dan karenanya tidak boleh dihapus. Kompiler salah.
Solusi sederhana (selain yang telah Anda sebutkan) adalah tidak mendeklarasikan Derived::Derieved()
sama sekali. Tampaknya dibuat secara implisit dengan benar dalam kasus itu.
Menambahkan noexcept akan menghasilkan error compiler internal
Ini juga merupakan bug kompilator.
Mengapa = default menghapus fungsi di GCC dalam kasus ini?
Apakah ini bug di GCC atau tidak (MSVC berperilaku serupa tetapi clang-cl menerima kode, sebagaimana adanya) adalah masalah bagi mereka yang lebih dipelajari dalam Standar C ++. Namun, tampaknya pengadu mengambil = default
untuk menyiratkan bahwa Derived
konstruktor bergantung pada (atau setara dengan ) konstruktor default untuk Base
- yang pasti dihapus, karena Anda telah mendefinisikan konstruktor (non-default) lain.
Namun, secara eksplisit menambahkan konstruktor default Anda sendiri , dengan Derived() {}
menghapus ketergantungan yang tersirat.
Ini dikonfirmasi (di GCC dan MSVC) dengan menentukan (yaitu membatalkan penghapusan) konstruktor default untuk Base
kelas:
class Base {
int x;
public:
Base() : x{0} {} // Adding this removes the error!
// Base() = default; // Also works
Base(int x): x{x} {}
virtual void f() = 0;
};
class Derived : public virtual Base {
public:
Derived() = default;
};
class Concrete: public Derived {
public:
Concrete(): Base{42} {}
void f() override {}
};
EDIT : Ini mungkin juga relevan, atau bahkan kemungkinan duplikat: Mengapa konstruktor default disebut warisan virtual?