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

__declspec
Palavras-chave