La optimización de la base vacía es excelente. Sin embargo, viene con la siguiente restricción:
La optimización de base vacía está prohibida si una de las clases base vacías es también el tipo o la base del tipo del primer miembro de datos no estático, ya que los dos subobjetos base del mismo tipo deben tener direcciones diferentes dentro de la representación del objeto del tipo más derivado.
Para explicar esta restricción, considere el siguiente código. El static_assert
fallará. Mientras que cambiar Foo
o Bar
heredar de en su lugar Base2
evitará el error:
#include <cstddef>
struct Base {};
struct Base2 {};
struct Foo : Base {};
struct Bar : Base {
Foo foo;
};
static_assert(offsetof(Bar,foo)==0,"Error!");
Entiendo este comportamiento completamente. Lo que no entiendo es por qué existe este comportamiento particular . Obviamente se agregó por una razón, ya que es una adición explícita, no un descuido. ¿Cuál es la razón de esto?
En particular, ¿por qué los dos subobjetos básicos deben tener direcciones diferentes? En lo anterior, Bar
es un tipo y foo
es una variable miembro de ese tipo. No veo por qué la clase base de Bar
asuntos importa a la clase base del tipo de foo
, o viceversa.
De hecho, yo, en todo caso, esperaría que &foo
sea la misma que la dirección de la Bar
instancia que lo contiene, como se requiere en otras situaciones (1) . Después de todo, no estoy haciendo nada elegante con la virtual
herencia, las clases base están vacías de todos modos, y la compilación con Base2
muestra que nada se rompe en este caso particular.
Pero claramente este razonamiento es incorrecto de alguna manera, y hay otras situaciones en las que se requeriría esta limitación.
Digamos que las respuestas deberían ser para C ++ 11 o más reciente (actualmente estoy usando C ++ 17).
(1) Nota: EBO se actualizó en C ++ 11, y en particular se convirtió en obligatorio para StandardLayoutType
s (aunque Bar
, arriba, no es a StandardLayoutType
).
fuente
Base *a = new Bar(); Base *b = a->foo;
cona==b
, peroa
yb
son objetos claramente diferentes (quizás con anulaciones de métodos virtuales diferentes).Respuestas:
Ok, parece que me he equivocado todo el tiempo, ya que para todos mis ejemplos debe existir una tabla v para el objeto base, lo que evitaría la optimización de la base vacía para comenzar. Dejaré los ejemplos en pie ya que creo que dan algunos ejemplos interesantes de por qué las direcciones únicas son normalmente una buena opción.
Habiendo estudiado todo esto más a fondo, no hay ninguna razón técnica para que la optimización de la clase base vacía se deshabilite cuando el primer miembro es del mismo tipo que la clase base vacía. Esto es solo una propiedad del modelo de objetos C ++ actual.
Pero con C ++ 20 habrá un nuevo atributo
[[no_unique_address]]
que le indica al compilador que un miembro de datos no estático puede no necesitar una dirección única (técnicamente hablando, es posible que se superponga [intro.object] / 7 ).Esto implica que (énfasis mío)
por lo tanto, se puede "reactivar" la optimización de la clase base vacía al darle al primer miembro de datos el atributo
[[no_unique_address]]
. Agregué un ejemplo aquí que muestra cómo funciona esto (y todos los demás casos que se me ocurren).Ejemplos incorrectos de problemas a través de esto
Como parece que una clase vacía puede no tener métodos virtuales, permítanme agregar un tercer ejemplo:
Pero las dos últimas llamadas son iguales.
Ejemplos antiguos (Probablemente no responda la pregunta ya que las clases vacías pueden no contener métodos virtuales, parece)
Considere en su código anterior (con destructores virtuales agregados) el siguiente ejemplo
Pero, ¿cómo debería el compilador distinguir estos dos casos?
Y tal vez un poco menos artificial:
¡Pero los dos últimos son iguales si tenemos una optimización de clase base vacía!
fuente
std::is_empty
en cppreference es mucho más elaborado. Misma desde el actual proyecto de eel.is .dynamic_cast
cuando no es polimórfico (con pequeñas excepciones que no son relevantes aquí).