empty_bases

Específicos de Microsoft

El C++ estándar requiere que un objeto más derivado tenga un tamaño distinto de cero y que ocupe uno o varios bytes de almacenamiento. Dado que el requisito solo se extiende a los objetos más derivados, los subobjetos de la clase base no están sujetos a esta restricción. La optimización vacía de clase base (EBCO) aprovecha esta libertad. El resultado es un menor consumo de memoria, lo que puede mejorar el rendimiento. El compilador de Microsoft Visual C++ ha tenido históricamente compatibilidad limitada con EBCO. En la actualización 3 de Visual Studio 2015 y versiones posteriores, hemos agregado un nuevo atributo __declspec(empty_bases) para los tipos de clase que aprovecha al máximo esta optimización.

Importante

El uso de __declspec(empty_bases) puede provocar un cambio importante de ABI en la estructura y el diseño de la clase donde se aplica. Asegúrese de que todo el código del cliente usa las mismas definiciones para estructuras y clases que el código al usar este atributo de clase de almacenamiento.

Sintaxis

__declspec( empty_bases )

Comentarios

En Visual Studio, en ausencia de alguna especificación __declspec(align()) o alignas(), una clase vacía tiene un tamaño de 1 byte:

struct Empty1 {};
static_assert(sizeof(Empty1) == 1, "Empty1 should be 1 byte");

Una clase con un único miembro de datos no estático de tipo char también tiene un tamaño de 1 byte:

struct Struct1
{
  char c;
};
static_assert(sizeof(Struct1) == 1, "Struct1 should be 1 byte");

La combinación de estas clases en una jerarquía de clases también da como resultado una clase con un tamaño de 1 byte:

struct Derived1 : Empty1
{
  char c;
};
static_assert(sizeof(Derived1) == 1, "Derived1 should be 1 byte");

El resultado es la optimización de la clase base vacía en el trabajo, ya que sin él Derived1 tendría un tamaño de 2 bytes: 1 byte para Empty1 y 1 byte para Derived1::c. El diseño de clase también es óptimo cuando hay una cadena de clases vacías:

struct Empty2 : Empty1 {};
struct Derived2 : Empty2
{
  char c;
};
static_assert(sizeof(Derived2) == 1, "Derived2 should be 1 byte");

Sin embargo, el diseño de clase predeterminado en Visual Studio no aprovecha las ventajas de EBCO en varios escenarios de herencia:

struct Empty3 {};
struct Derived3 : Empty2, Empty3
{
  char c;
};
static_assert(sizeof(Derived3) == 1, "Derived3 should be 1 byte"); // Error

Aunque Derived3 podría tener un tamaño de 1 byte, el diseño de clase predeterminado da como resultado un tamaño de 2 bytes. El algoritmo de diseño de clase agrega 1 byte de relleno entre dos clases base vacías consecutivas, lo que hace que Empty2 consuma un byte adicional dentro de Derived3:

class Derived3  size(2):
   +---
0  | +--- (base class Empty2)
0  | | +--- (base class Empty1)
   | | +---
   | +---
1  | +--- (base class Empty3)
   | +---
1  | c
   +---

Los efectos de este diseño poco óptimo se componen cuando los requisitos de alineación de una clase base posterior o subobjeto miembro obligan a un relleno adicional:

struct Derived4 : Empty2, Empty3
{
  int i;
};
static_assert(sizeof(Derived4) == 4, "Derived4 should be 4 bytes"); // Error

La alineación natural de un objeto de tipo int es de 4 bytes, por lo que se deben agregar 3 bytes de relleno adicional después de Empty3 para alinear correctamente a Derived4::i:

class Derived4 size(8):
   +---
0  | +--- (base class Empty2)
0  | | +--- (base class Empty1)
   | | +---
   | +---
1  | +--- (base class Empty3)
   | +---
   | <alignment member> (size=3)
4  | i
   +---

Otro problema con el diseño de clase predeterminado es que una clase base vacía se puede colocar en un desplazamiento más allá del final de la clase:

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)
   | +---
   +---

Aunque Struct2 es el tamaño óptimo, Empty1 se establece en el desplazamiento 1 dentro de Struct2 pero el tamaño de Struct2 no aumenta para tenerse en cuenta. Como resultado, para una matriz A de objetos Struct2, la dirección del subobjeto Empty1 de A[0] será la misma que la dirección de A[1], que no debería ser el caso. Este problema no se produciría si Empty1 se estableciera en el desplazamiento 0 dentro de Struct2, superponiendo así el subobjeto Struct1.

El algoritmo de diseño predeterminado no se ha modificado para abordar estas limitaciones y aprovechar completamente a EBCO. Este cambio interrumpiría la compatibilidad binaria. Si el diseño predeterminado de una clase ha cambiado como resultado de EBCO, todos los archivos de objeto y bibliotecas que contengan la definición de clase deberán volver a compilarse para que todos acepten el diseño de clase. Este requisito también se extendería a las bibliotecas obtenidas de orígenes externos. Los desarrolladores de estas bibliotecas tendrían que proporcionar versiones independientes compiladas con y sin el diseño de EBCO para admitir a los clientes que usan diferentes versiones del compilador. Aunque no podemos cambiar el diseño predeterminado, podemos proporcionar un medio para cambiar el diseño por clase con la adición del atributo de clase __declspec(empty_bases). Una clase definida con este atributo puede hacer pleno uso de 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 los subobjetos de Derived3 se colocan en el desplazamiento 0 y su tamaño es el óptimo de 1 byte. Un punto importante que hay que recordar es que __declspec(empty_bases) solo afecta al diseño de la clase a la que se aplica. No se aplica recursivamente a las clases 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
   | +---
   +---

Aunque __declspec(empty_bases) se aplica a Derived5, no es apto para EBCO porque no tiene clases base vacías directas, por lo que no tiene efecto. Sin embargo, si en su lugar se aplica a la clase base Derived4, que es apta para EBCO, tanto Derived4 como Derived5 tendrá un diseño óptimo:

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
   | +---
   +---

Debido al requisito de que todos los archivos de objetos y bibliotecas acepten el diseño de clase, __declspec(empty_bases) solo se puede aplicar a las clases que controla. No se puede aplicar a las clases de la biblioteca estándar ni a las clases incluidas en las bibliotecas que tampoco se vuelven a compilar con el diseño EBCO.

FIN de Específicos de Microsoft

Consulte también

__declspec
Palabras clave