A otimização da base vazia é ótima. No entanto, ele vem com a seguinte restrição:
A otimização de base vazia é proibida se uma das classes de base vazias também for o tipo ou a base do tipo do primeiro membro de dados não estático, pois os dois subobjetos de base do mesmo tipo precisam ter endereços diferentes na representação do objeto do tipo mais derivado.
Para explicar essa restrição, considere o seguinte código. O static_assert
irá falhar. Considerando que, alterar Foo
ou Bar
herdar em vez disso Base2
evitará o erro:
#include <cstddef>
struct Base {};
struct Base2 {};
struct Foo : Base {};
struct Bar : Base {
Foo foo;
};
static_assert(offsetof(Bar,foo)==0,"Error!");
Eu entendo esse comportamento completamente. O que não entendo é por que esse comportamento específico existe . Obviamente, foi adicionado por um motivo, pois é uma adição explícita, não uma supervisão. Qual é a justificativa para isso?
Em particular, por que os dois subobjetos de base devem ter endereços diferentes? No acima, Bar
é um tipo e foo
é uma variável de membro desse tipo. Não vejo por que a classe base Bar
importa para a classe base do tipo de foo
, ou vice-versa.
De fato, eu espero que &foo
seja o mesmo que o endereço da Bar
instância que o contém - como é necessário em outras situações (1) . Afinal, eu não estou fazendo nada sofisticado com virtual
herança, as classes base estão vazias de qualquer maneira, e a compilação Base2
mostra que nada quebra nesse caso específico.
Mas claramente esse raciocínio está incorreto de alguma maneira, e há outras situações em que essa limitação seria necessária.
Digamos que as respostas devam ser para o C ++ 11 ou mais recente (atualmente estou usando o C ++ 17).
(1) Nota: O EBO foi atualizado no C ++ 11 e, em particular, tornou-se obrigatório para StandardLayoutType
s (embora Bar
, acima, não seja a StandardLayoutType
).
fonte
Base *a = new Bar(); Base *b = a->foo;
coma==b
, masa
eb
claramente objetos diferentes (talvez com substituições de métodos virtuais diferentes).Respostas:
Ok, parece que eu errei o tempo todo, pois, para todos os meus exemplos, é necessário que exista uma vtable para o objeto base, o que impediria a otimização da base vazia. Deixarei que os exemplos permaneçam, pois acho que eles dão alguns exemplos interessantes de por que endereços únicos são normalmente uma coisa boa de se ter.
Tendo estudado tudo isso mais profundamente, não há razão técnica para a otimização da classe base vazia ser desativada quando o primeiro membro é do mesmo tipo que a classe base vazia. Isso é apenas uma propriedade do atual modelo de objeto C ++.
Porém, com o C ++ 20, haverá um novo atributo
[[no_unique_address]]
que informa ao compilador que um membro de dados não estático pode não precisar de um endereço exclusivo (tecnicamente falando, ele está potencialmente sobreposto [intro.object] / 7 ).Isso implica que (ênfase minha)
portanto, é possível "reativar" a otimização da classe base vazia, atribuindo o atributo ao primeiro membro de dados
[[no_unique_address]]
. Adicionei um exemplo aqui que mostra como isso (e todos os outros casos em que eu conseguia pensar) funciona.Exemplos errados de problemas através deste
Como parece que uma classe vazia pode não ter métodos virtuais, deixe-me adicionar um terceiro exemplo:
Mas as duas últimas ligações são iguais.
Exemplos antigos (provavelmente não respondem à pergunta, pois as classes vazias podem não conter métodos virtuais, ao que parece)
Considere no seu código acima (com destruidores virtuais adicionados) o exemplo a seguir
Mas como o compilador deve distinguir esses dois casos?
E talvez um pouco menos artificial:
Mas os dois últimos são os mesmos se tivermos otimização de classe base vazia!
fonte
std::is_empty
na cppreference é muito mais elaborado. Mesmo a partir do actual projecto em eel.is .dynamic_cast
quando não é polimórfico (com pequenas exceções não relevantes aqui).