Konstruktor kosong default yang aneh pada perilaku warisan virtual di GCC

Dec 14 2020

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() = defaultatau mendefinisikan konstruktor kosong pada kelas Base.

Mengapa = defaultpenghapusan fungsi di GCC dalam kasus ini?

Jawaban

2 eerorika Dec 14 2020 at 18:29

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.

Derivedbersifat abstrak (karena tidak mengimplementasikan semua fungsi virtual murni), dan Basemerupakan 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.

1 AdrianMole Dec 14 2020 at 18:02

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 = defaultuntuk menyiratkan bahwa Derivedkonstruktor 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 Basekelas:

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?