Mengapa optimasi basis kosong dilarang ketika kelas dasar kosong juga merupakan variabel anggota?

14

Optimasi basis kosong sangat bagus. Namun, ia datang dengan batasan berikut:

Optimalisasi basis kosong dilarang jika salah satu kelas basis kosong juga merupakan tipe atau basis tipe anggota data non-statis pertama, karena dua sub-objek dasar dari tipe yang sama diharuskan memiliki alamat yang berbeda dalam representasi objek dari tipe yang paling diturunkan.

Untuk menjelaskan batasan ini, pertimbangkan kode berikut. The static_assertakan gagal. Sedangkan, mengubah salah satu Fooatau Barsebaliknya mewarisi dari Base2akan mencegah kesalahan:

#include <cstddef>

struct Base  {};
struct Base2 {};

struct Foo : Base {};

struct Bar : Base {
    Foo foo;
};

static_assert(offsetof(Bar,foo)==0,"Error!");

Saya mengerti perilaku ini sepenuhnya. Apa yang saya tidak mengerti adalah mengapa perilaku tertentu ada . Jelas ditambahkan karena suatu alasan, karena ini merupakan tambahan eksplisit, bukan pengawasan. Apa alasan untuk ini?

Khususnya, mengapa dua sub-proyek dasar harus memiliki alamat yang berbeda? Di atas, Baradalah tipe dan foovariabel anggota tipe itu. Saya tidak melihat mengapa kelas dasar Barpenting untuk kelas dasar dari jenis foo, atau sebaliknya.

Memang, saya jika ada, saya harapkan itu &foosama dengan alamat Barinstance yang berisi itu — seperti yang diperlukan dalam situasi lain (1) . Lagipula, saya tidak melakukan apa-apa dengan virtualwarisan, kelas dasar kosong, dan kompilasi dengan Base2menunjukkan bahwa tidak ada yang rusak dalam kasus khusus ini.

Tetapi jelas alasan ini tidak benar, dan ada situasi lain di mana batasan ini diperlukan.

Katakanlah jawaban harus untuk C ++ 11 atau lebih baru (Saat ini saya menggunakan C ++ 17).

(1) Catatan: EBO ditingkatkan di C ++ 11, dan khususnya menjadi wajib untuk StandardLayoutTypes (meskipun Bar, di atas, bukan a StandardLayoutType).

Imallett
sumber
4
Bagaimana alasan yang Anda kutip (" karena dua sub-objek dasar dari jenis yang sama harus memiliki alamat yang berbeda ") gagal? Objek yang berbeda dari jenis yang sama harus memiliki alamat yang berbeda, dan persyaratan ini memastikan kami tidak melanggar aturan itu. Jika optimasi basis kosong diterapkan di sini, kita dapat melakukannya Base *a = new Bar(); Base *b = a->foo;dengan a==b, tetapi adan bjelas merupakan objek yang berbeda (mungkin dengan metode virtual yang berbeda)
Toby Speight
1
Jawaban pengacara bahasa mengutip bagian yang relevan dari spesifikasi. Dan sepertinya Anda sudah tahu tentang itu.
Deduplicator
3
Saya tidak yakin saya mengerti jawaban seperti apa yang Anda cari di sini. Model objek C ++ adalah apa adanya. Pembatasan ada karena model objek memerlukannya. Apa lagi yang Anda cari di luar itu?
Nicol Bolas
@TobySpeight Objek berbeda dari tipe yang sama diperlukan untuk memiliki alamat yang berbeda Sangat mudah untuk melanggar aturan ini dalam sebuah program dengan perilaku yang terdefinisi dengan baik.
Pengacara Bahasa
@TobySpeight Tidak, saya tidak bermaksud bahwa Anda lupa mengatakan tentang masa hidup: "Objek berbeda dengan tipe yang sama dengan masa hidup mereka " . Dimungkinkan untuk memiliki beberapa objek dengan tipe yang sama, semuanya hidup, pada alamat yang sama. Setidaknya ada 2 bug di kata-kata yang memungkinkan ini.
Pengacara Bahasa

Jawaban:

4

Ok, sepertinya saya salah sepanjang waktu, karena untuk semua contoh saya perlu ada vtable untuk objek dasar, yang akan mencegah pengoptimalan basis kosong untuk memulai. Saya akan membiarkan contoh berdiri karena saya pikir mereka memberikan beberapa contoh menarik mengapa alamat unik biasanya merupakan hal yang baik untuk dimiliki.

Setelah mempelajari keseluruhan ini secara lebih mendalam, tidak ada alasan teknis untuk optimasi kelas dasar kosong untuk dinonaktifkan ketika anggota pertama adalah dari jenis yang sama dengan kelas dasar kosong. Ini hanya properti model objek C ++ saat ini.

Tetapi dengan C ++ 20 akan ada atribut baru [[no_unique_address]]yang memberitahu kompiler bahwa anggota data non-statis mungkin tidak memerlukan alamat unik (secara teknis itu berpotensi tumpang tindih [intro.object] / 7 ).

Ini menyiratkan bahwa (penekanan milikku)

Anggota data non-statis dapat berbagi alamat anggota data non-statis lain atau dari kelas dasar , [...]

maka seseorang dapat "mengaktifkan kembali" optimasi kelas dasar kosong dengan memberikan atribut anggota data pertama [[no_unique_address]]. Saya menambahkan contoh di sini yang menunjukkan bagaimana ini (dan semua kasus lain yang bisa saya pikirkan) berfungsi.

Contoh masalah yang salah melalui ini

Karena tampaknya kelas kosong mungkin tidak memiliki metode virtual, izinkan saya menambahkan contoh ketiga:

int stupid_method(Base *b) {
  if( dynamic_cast<Foo*>(b) ) return 0;
  if( dynamic_cast<Bar*>(b) ) return 1;
  return 2;
}

Bar b;
stupid_method(&b);  // Would expect 0
stupid_method(&b.foo); //Would expect 1

Tetapi dua panggilan terakhir adalah sama.

Contoh lama (Mungkin tidak menjawab pertanyaan karena kelas kosong mungkin tidak berisi metode virtual, tampaknya)

Pertimbangkan dalam kode Anda di atas (dengan menambahkan destruktor virtual) contoh berikut

void delBase(Base *b) {
    delete b;
}

Bar *b = new Bar;
delBase(b); // One would expect this to be absolutely fine.
delBase(&b->foo); // Whoaa, we shouldn't delete a member variable.

Tetapi bagaimana kompiler harus membedakan kedua kasus ini?

Dan mungkin sedikit kurang dibikin:

struct Base { 
  virtual void hi() { std::cout << "Hello\n";}
};

struct Foo : Base {
  void hi() override { std::cout << "Guten Tag\n";}
};

struct Bar : Base {
    Foo foo;
};

Bar b;
b.hi() // Hello
b.foo.hi() // Guten Tag
Base *a = &b;
Base *z = &b.foo;
a->hi() // Hello
z->hi() // Guten Tag

Tetapi dua yang terakhir sama jika kita memiliki optimasi kelas dasar kosong!

n314159
sumber
1
Orang bisa berargumentasi bahwa panggilan kedua memiliki perilaku yang tidak jelas. Jadi kompiler tidak harus membedakan apa pun.
StoryTeller - Unslander Monica
1
Kelas dengan anggota virtual apa pun tidak kosong, jadi tidak relevan di sini!
Deduplicator
1
@Deduplicator Apakah Anda memiliki kutipan standar untuk itu? Cppref memberitahu kita bahwa kelas kosong adalah "kelas atau struct yang tidak memiliki anggota data non-statis".
n314159
1
@ n314159 std::is_empty pada cppreference jauh lebih rumit. Sama dari rancangan saat ini di eel.is .
Deduplicator
2
Anda tidak dapat dynamic_castketika itu bukan polimorfik (dengan pengecualian kecil tidak relevan di sini).
TC