c++ pod c++11 standard-layout

¿Por qué la definición de "diseño estándar" POD de C++ 11 es como es?



c++11 standard-layout (6)

Estoy investigando la definición nueva y relajada de POD en C++11 (sección 9.7)

Una clase de diseño estándar es una clase que:

  • no tiene miembros de datos no estáticos de clase de diseño no estándar (o matriz de tales tipos) o referencia,
  • no tiene funciones virtuales (10.3) ni clases base virtuales (10.1),
  • tiene el mismo control de acceso (Cláusula 11) para todos los miembros de datos no estáticos,
  • no tiene clases base de diseño no estándar,
  • o bien no tiene miembros de datos no estáticos en la clase más derivada y, como máximo, una clase base con miembros de datos no estáticos , o no tiene clases base con miembros de datos no estáticos, y
  • no tiene clases base del mismo tipo que el primer miembro de datos no estáticos .

He resaltado los bits que me sorprendieron.

¿Qué iría mal si toleramos miembros de datos con controles de acceso variables?

¿Qué iría mal si el primer miembro de datos también fuera una clase base? es decir

struct Foo {}; struct Good : Foo {int x; Foo y;}; struct Bad : Foo {Foo y; int x;};

Admito que es una construcción extraña, pero ¿por qué debería Bad prohibirse pero no ser Good ?

Finalmente, ¿qué iría mal si más de una clase constituyente tuviera miembros de datos?


¿Qué iría mal si toleramos miembros de datos con controles de acceso variables?

El lenguaje actual dice que el compilador no puede reordenar miembros bajo el mismo control de acceso. Me gusta:

struct x { public: int x; int y; private: int z; };

Aquí x debe asignarse antes de y, pero no hay restricción en z relativa a xey.

struct y { public: int x; public: int y; };

La nueva redacción dice que y sigue siendo un POD a pesar de los dos public . Esto es en realidad una relajación de las reglas.


Básicamente se trata de la compatibilidad con C ++ 03 y C:

  • mismo control de acceso: las implementaciones de C ++ 03 pueden usar especificadores de control de acceso como una oportunidad para reordenar los (grupos de) miembros de una clase, por ejemplo para empaquetarlos mejor.
  • más de una clase en la jerarquía con miembros de datos no estáticos: C ++ 03 no dice dónde se encuentran las clases base, o si el relleno se elimina en subobjetos de clase base que estarían presentes en un objeto completo del mismo tipo.
  • clase base y primer miembro del mismo tipo: debido a la segunda regla, si el tipo de clase base se usa para un miembro de datos, entonces debe ser una clase vacía. Muchos compiladores implementan la optimización de la clase base vacía, por lo que lo que dice Andreas sobre los subobjetos que tienen la misma dirección sería cierto. No estoy seguro de qué se trata de las clases de diseño estándar, lo que significa que es malo para el subobjeto de clase base tener la misma dirección que un primer miembro de datos del mismo tipo, pero no importa cuando el subobjeto de clase base tiene la misma dirección que un primer miembro de datos de un tipo diferente. [Editar: se debe a que diferentes objetos del mismo tipo tienen direcciones diferentes, incluso si son sub-objetos vacíos. Gracias a Johannes]

C ++ 0x probablemente podría haber definido que esas cosas también son tipos de diseño estándar, en cuyo caso también definiría cómo están distribuidos, en la misma medida que lo hace para los tipos de diseño estándar. La respuesta de Johannes va más allá, mire su ejemplo de una buena propiedad de las clases de diseño estándar con las que estas cosas interfieren.

Pero si lo hiciera, algunas implementaciones se verían obligadas a cambiar la forma en que diseñan las clases para que coincidan con los nuevos requisitos, lo que es una molestia para la compatibilidad de estructuras entre diferentes versiones de ese compilador antes y después de C ++ 0x. Rompe el C ++ ABI, básicamente.

Mi comprensión de cómo se definió el diseño estándar es que analizaron los requisitos de POD que se podrían relajar sin romper las implementaciones existentes. Así que supongo, sin comprobar, que los ejemplos anteriores son ejemplos en los que algunas implementaciones existentes de C ++ 03 usan la naturaleza no POD de la clase para hacer algo que es incompatible con el diseño estándar.


De la viñeta 5, parece que ambas no son pod, ya que la clase más derivada tiene un miembro de datos no estáticos (el int), no puede tener una clase base con un miembro de datos no estático.

Lo entiendo como: "solo una de las clases" base "(es decir, la clase misma o una de las clases heredadas) puede tener miembros de datos no estáticos"


En cuanto a por qué Bad no está permitido, permítanme hacer una cita de un artículo que encontré:

Esto garantiza que dos subobjetos que tienen el mismo tipo de clase y que pertenecen al mismo objeto derivado de la mayoría no se asignan a la misma dirección.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2172.html


Se le permite lanzar una dirección de objeto de clase de diseño estándar a un puntero a su primer miembro y viceversa por uno de los párrafos posteriores, que también se hace a menudo en C:

struct A { int x; }; A a; // "px" is guaranteed to point to a.x int *px = (int*) &a; // guaranteed to point to a A *pa = (A*)px;

Para que eso funcione, el primer miembro y el objeto completo deben tener la misma dirección (el compilador no puede ajustar el puntero int por ningún byte porque no puede saber si es miembro de A o no).

Finalmente, ¿qué iría mal si más de una clase constituyente tuviera miembros de datos?

Dentro de una clase, los miembros se asignan en direcciones crecientes de acuerdo con el orden de la declaración. Sin embargo, C ++ no dicta el orden de asignación para los miembros de datos en todas las clases. Si tanto la clase derivada como la clase base tenían miembros de datos, el Estándar no define un orden para sus direcciones a propósito, para dar a la implementación una flexibilidad total en la disposición de la memoria. Pero para que funcione el elenco anterior, debe saber cuál es el "primer" miembro en orden de asignación.

¿Qué iría mal si el primer miembro de datos también fuera una clase base?

Si la clase base tiene el mismo tipo que el primer miembro de datos, las implementaciones que colocan las clases base antes de los objetos de clase derivados en memoria necesitarían tener un byte de relleno antes de los miembros de datos de objetos de clase derivados en la memoria (la clase base tendría un tamaño ), para evitar tener la misma dirección tanto para la clase base como para el primer miembro de datos (en C ++, dos objetos distintos del mismo tipo siempre tienen direcciones diferentes). Pero eso volvería a hacer imposible convertir la dirección del objeto de clase derivado al tipo de su primer miembro de datos.


struct Good tampoco es un diseño estándar, ya que Foo y Good tienen miembros de datos no estáticos.

De esta manera, Good debería ser:

struct Foo {int foo;}; struct Good : public Foo {Foo y;};

que no cumple con la 6ª bala De ahí la sexta bala?