Boş taban sınıfı da üye değişken olduğunda boş taban optimizasyonu neden yasaklanmıştır?


14

Boş taban optimizasyonu harika. Ancak, aşağıdaki kısıtlama ile birlikte gelir:

Aynı tabandaki iki temel alt nesnenin nesne temsili içinde farklı adreslere sahip olması gerektiğinden, boş taban sınıflarından birinin de ilk statik olmayan veri üyesinin türü veya tabanı olması durumunda boş taban optimizasyonu yasaktır. türetilmiştir.

Bu kısıtlamayı açıklamak için aşağıdaki kodu göz önünde bulundurun. static_assertBaşarısız olur. Halbuki ikisinden birini değiştirmek ya Fooda Bardevralmak yerine Base2hatayı önlemek için:

#include <cstddef>

struct Base  {};
struct Base2 {};

struct Foo : Base {};

struct Bar : Base {
    Foo foo;
};

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

Bu davranışı tamamen anlıyorum. Ne yapmak değil anlamaya olduğunu neden bu özel davranış var . Bir nedenden dolayı açık bir şekilde eklenmiştir, çünkü açık bir eklenti, bir gözetim değil. Bunun mantığı nedir?

Özellikle, iki temel alt nesnenin neden farklı adreslere sahip olması gerekir? Yukarıda, Barbir türdür ve foobu türün bir üye değişkenidir. Temel sınıfın neden temel sınıf Bartürüyle ilgili olduğunu anlamıyorum fooveya tam tersi.

Gerçekten de, eğer bir şey varsa, bunun onu içeren örneğin &fooadresi ile aynı olmasını beklerdim Bar- diğer durumlarda olması gerektiği gibi (1) . Sonuçta, virtualkalıtımla süslü bir şey yapmıyorum , temel sınıflar ne olursa olsun boş ve bu derleme Base2, bu özel durumda hiçbir şeyin kırılmadığını gösteriyor.

Ancak bu mantık bir şekilde yanlıştır ve bu sınırlamanın gerekli olacağı başka durumlar da vardır.

Diyelim ki cevaplar C ++ 11 veya daha yeni bir sürüm için olmalı (şu anda C ++ 17 kullanıyorum).

(1) Not: EBO, C ++ 11'de yükseltildi ve özellikle StandardLayoutTypes için zorunlu hale geldi (ancak Bar, yukarıda a değil StandardLayoutType).


4
Alıntıladığınız gerekçe (" aynı türdeki iki temel alt nesnenin farklı adreslere sahip olması gerektiğinden ") nasıl kısalır? Farklı adreslere sahip olmak için aynı türden farklı nesneler gerekir ve bu gereksinim bu kuralı ihlal etmememizi sağlar. Boş taban optimizasyonu burada başvuru yaptıysanız, olabilir Base *a = new Bar(); Base *b = a->foo;ile a==b, ancak ave baçıkça (belki farklı sanal yöntem geçersiz kılma özelliği olan) farklı nesnelerdir.
Toby Speight

1
Dil-avukat cevabı spesifikasyonun ilgili kısımlarından alıntı yapıyor. Ve bunu zaten biliyor gibisin.
Deduplicator

3
Burada ne tür bir cevap aradığınızı anladığımdan emin değilim. C ++ nesne modeli budur. Kısıtlama var çünkü nesne modeli gerektiriyor. Bunun ötesinde daha ne arıyorsun?
Nicol Bolas

@TobySpeight Farklı adreslere sahip olmak için aynı türden farklı nesneler gerekir İyi tanımlanmış davranışa sahip bir programda bu kuralı kırmak kolayca mümkündür.
Language Lawyer

: @TobySpeight Hayır, ömür boyu hakkında söylenecek unuttum anlamına gelmez "aynı türden farklı nesnenin kendi ömrünü Withing " . Aynı adresten, hepsi canlı olan, aynı adreste birden fazla nesneye sahip olmak mümkündür. İfadede buna izin veren en az 2 hata vardır.
Language Lawyer

Yanıtlar:


4

Tamam, her zaman yanlış yapmışım gibi görünüyor, çünkü tüm örneklerim için, temel nesne için boş bir taban optimizasyonunun başlamasını önleyecek bir vtable olması gerekiyor. Örneklerin durmasına izin vereceğim çünkü benzersiz adreslerin normalde neden iyi bir şey olduğuna dair ilginç örnekler verdiklerini düşünüyorum.

Bütün bunları daha derinlemesine inceledikten sonra, ilk üye boş temel sınıfla aynı türdeyse boş temel sınıf optimizasyonunun devre dışı bırakılması için teknik bir neden yoktur. Bu sadece geçerli C ++ nesne modelinin bir özelliğidir.

Ancak C ++ 20 ile [[no_unique_address]], derleyiciye statik olmayan bir veri üyesinin benzersiz bir adrese ihtiyaç duymayabileceğini söyleyen yeni bir öznitelik olacaktır (teknik olarak, potansiyel olarak çakışan [intro.object] / 7 ).

Bu (benim vurgu)

Statik olmayan veri üyesi, başka bir statik olmayan veri üyesinin veya bir temel sınıfın adresini paylaşabilir , [...]

dolayısıyla, ilk veri üyesine öznitelik vererek boş temel sınıf optimizasyonunu "yeniden etkinleştirebilir" [[no_unique_address]]. Buraya (ve aklıma gelen diğer tüm vakaların) nasıl çalıştığını gösteren bir örnek ekledim .

Bu sayede yanlış sorun örnekleri

Boş bir sınıfın sanal yöntemleri olmayabilir gibi göründüğünden, üçüncü bir örnek ekleyeyim:

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

Ancak son iki çağrı aynı.

Eski örnekler (Muhtemelen soruyu cevaplamayın çünkü boş sınıflar sanal yöntemler içermeyebilir, öyle görünüyor)

Yukarıdaki kodunuzu (eklenen sanal yıkıcılar ile) aşağıdaki örneği düşünün

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.

Ancak derleyici bu iki durumu nasıl ayırt etmelidir?

Ve belki biraz daha az çelişkili:

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

Ancak boş temel sınıf optimizasyonumuz varsa son ikisi aynıdır!


1
Yine de, ikinci çağrının tanımlanmamış bir davranışa sahip olduğu iddia edilebilir. Yani derleyici hiçbir şeyi ayırt etmek zorunda değil.
StoryTeller - Unlander Monica

1
Herhangi bir sanal üyeye sahip bir sınıf boş değil, bu yüzden burada alakasız!
Deduplicator

1
@Deduplicator Bu konuda standart bir teklifiniz var mı? Cppref bize boş bir sınıfın "statik olmayan veri üyesi olmayan bir sınıf veya yapı" olduğunu söyler.
n314159

1
@ n314159 std::is_emptycppreference konusunda çok daha ayrıntılı. Eel.is'deki mevcut taslaktan aynı .
Deduplicator

2
Yapamazsınız dynamic_casto (küçük istisnalar değil alakalı burada birlikte) polimorfik olmadığı zamanlarda.
TC
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.