empty_bases
Seção específica da Microsoft
O padrão C++ exige que um objeto mais derivado tenha um tamanho diferente de zero e deve ocupar um ou mais bytes de armazenamento. Como o requisito se estende apenas a objetos mais derivados, os subobjetos de classe base não estão sujeitos a essa restrição. A EBCO (Otimização de Classe Base Vazia) aproveita essa liberdade. Isso resulta na redução do consumo de memória, o que pode melhorar o desempenho. Historicamente, o compilador Microsoft Visual C++ tem suporte limitado para EBCO. No Visual Studio 2015 Atualização 3 e versões posteriores, adicionamos um novo atributo __declspec(empty_bases)
para tipos de classe que aproveitam ao máximo essa otimização.
Importante
O uso de __declspec(empty_bases)
pode causar uma alteração interruptiva de ABI na estrutura e no layout de classe em que ele é aplicado. Certifique-se de que todo o código do cliente use as mesmas definições para estruturas e classes que seu código ao usar esse atributo de classe de armazenamento.
Sintaxe
__declspec( empty_bases )
Comentários
No Visual Studio, sem nenhuma especificação __declspec(align())
ou alignas()
, uma classe vazia tem um byte de tamanho:
struct Empty1 {};
static_assert(sizeof(Empty1) == 1, "Empty1 should be 1 byte");
Uma classe com um único membro de dados não estáticos do tipo char
também tem um byte de tamanho:
struct Struct1
{
char c;
};
static_assert(sizeof(Struct1) == 1, "Struct1 should be 1 byte");
A combinação dessas classes em uma hierarquia de classe também resulta em uma classe com um byte de tamanho:
struct Derived1 : Empty1
{
char c;
};
static_assert(sizeof(Derived1) == 1, "Derived1 should be 1 byte");
Esse resultado é a Otimização de Classe Base Vazia no trabalho, pois sem ela Derived1
seriam dois bytes de tamanho: um byte para Empty1
e um byte para Derived1::c
. O layout de classe também é ideal quando há uma cadeia de classes vazias:
struct Empty2 : Empty1 {};
struct Derived2 : Empty2
{
char c;
};
static_assert(sizeof(Derived2) == 1, "Derived2 should be 1 byte");
No entanto, o layout de classe padrão no Visual Studio não aproveita o EBCO em vários cenários de herança:
struct Empty3 {};
struct Derived3 : Empty2, Empty3
{
char c;
};
static_assert(sizeof(Derived3) == 1, "Derived3 should be 1 byte"); // Error
Embora Derived3
possa ter um byte de tamanho, o layout de classe padrão resulta em dois bytes de tamanho. O algoritmo de layout de classe está adicionando um byte de preenchimento entre duas classes base vazias consecutivas, resultando efetivamente no consumo Empty2
de um byte extra em Derived3
:
class Derived3 size(2):
+---
0 | +--- (base class Empty2)
0 | | +--- (base class Empty1)
| | +---
| +---
1 | +--- (base class Empty3)
| +---
1 | c
+---
Os efeitos desse layout de qualidade inferior são compostos quando os requisitos de alinhamento de uma classe base posterior ou do subobjeto membro forçam o preenchimento extra:
struct Derived4 : Empty2, Empty3
{
int i;
};
static_assert(sizeof(Derived4) == 4, "Derived4 should be 4 bytes"); // Error
O alinhamento natural de um objeto de tipo int
é de quatro bytes, portanto, três bytes de preenchimento extra devem ser adicionados depois Empty3
para alinhar corretamente Derived4::i
:
class Derived4 size(8):
+---
0 | +--- (base class Empty2)
0 | | +--- (base class Empty1)
| | +---
| +---
1 | +--- (base class Empty3)
| +---
| <alignment member> (size=3)
4 | i
+---
Outro problema com o layout de classe padrão é que uma classe base vazia pode ser colocada em um deslocamento após o final da classe:
struct Struct2 : Struct1, Empty1
{
};
static_assert(sizeof(Struct2) == 1, "Struct2 should be 1 byte");
class Struct2 size(1):
+---
0 | +--- (base class Struct1)
0 | | c
| +---
1 | +--- (base class Empty1)
| +---
+---
Embora Struct2
seja o tamanho ideal, Empty1
é colocado no deslocamento 1 dentro de Struct2
, mas o tamanho de Struct2
não é aumentado para considerar isso. Como resultado, para uma matriz A
de Struct2
objetos, o endereço do subobjeto Empty1
de A[0]
será o mesmo que o endereço de A[1]
, o que não deve ser o caso. Esse problema não ocorreria se Empty1
fosse disposto no deslocamento 0 dentro de Struct2
, sobrepondo-se assim ao subobjeto Struct1
.
O algoritmo de layout padrão não foi modificado para resolver essas limitações e aproveitar totalmente a EBCO. Essa alteração interromperia a compatibilidade binária. Se o layout padrão de uma classe for alterado como resultado da EBCO, cada biblioteca e arquivo de objeto que contém a definição de classe precisará ser recompilado para que todos concordem com o layout da classe. Esse requisito também se estenderia a bibliotecas obtidas de fontes externas. Os desenvolvedores dessas bibliotecas teriam que fornecer versões independentes compiladas com e sem o layout da EBCO para dar suporte a clientes que usam versões diferentes do compilador. Embora não possamos alterar o layout padrão, podemos fornecer um meio de alterar o layout por classe com a adição do atributo de classe __declspec(empty_bases)
. Uma classe definida com esse atributo pode fazer uso total da EBCO.
struct __declspec(empty_bases) Derived3 : Empty2, Empty3
{
char c;
};
static_assert(sizeof(Derived3) == 1, "Derived3 should be 1 byte"); // No Error
class Derived3 size(1):
+---
0 | +--- (base class Empty2)
0 | | +--- (base class Empty1)
| | +---
| +---
0 | +--- (base class Empty3)
| +---
0 | c
+---
Todos os subobjetos de Derived3
são dispostos no deslocamento 0 e seu tamanho é o um byte ideal. Um ponto importante a ser lembrado é que __declspec(empty_bases)
afeta apenas o layout da classe à qual ele é aplicado. Ele não é aplicado recursivamente às classes base:
struct __declspec(empty_bases) Derived5 : Derived4
{
};
static_assert(sizeof(Derived5) == 4, "Derived5 should be 4 bytes"); // Error
class Derived5 size(8):
+---
0 | +--- (base class Derived4)
0 | | +--- (base class Empty2)
0 | | | +--- (base class Empty1)
| | | +---
| | +---
1 | | +--- (base class Empty3)
| | +---
| | <alignment member> (size=3)
4 | | i
| +---
+---
Embora __declspec(empty_bases)
seja aplicado em Derived5
, ele não é qualificado para EBCO porque não tem classes base vazias diretas, portanto, não tem nenhum efeito. No entanto, se, em vez disso, for aplicado à classe base Derived4
, que é qualificada para EBCO, ambos Derived4
e Derived5
terão o layout ideal:
struct __declspec(empty_bases) Derived4 : Empty2, Empty3
{
int i;
};
static_assert(sizeof(Derived4) == 4, "Derived4 should be 4 bytes"); // No Error
struct Derived5 : Derived4
{
};
static_assert(sizeof(Derived5) == 4, "Derived5 should be 4 bytes"); // No Error
class Derived5 size(4):
+---
0 | +--- (base class Derived4)
0 | | +--- (base class Empty2)
0 | | | +--- (base class Empty1)
| | | +---
| | +---
0 | | +--- (base class Empty3)
| | +---
0 | | i
| +---
+---
Devido ao requisito de que todos os arquivos e bibliotecas de objetos concordem com o layout da classe, __declspec(empty_bases)
só pode ser aplicado às classes que você controla. Ele não pode ser aplicado a classes na biblioteca padrão ou a classes incluídas em bibliotecas que também não são recompiladas com o layout da EBCO.
Fim da seção específica da Microsoft
Confira também
Comentários
https://aka.ms/ContentUserFeedback.
Em breve: Ao longo de 2024, eliminaremos os problemas do GitHub como o mecanismo de comentários para conteúdo e o substituiremos por um novo sistema de comentários. Para obter mais informações, consulteEnviar e exibir comentários de