¿Dirección de subclase igual a dirección de clase base virtual?

8

Todos sabemos que cuando se usa una herencia simple simple, la dirección de una clase derivada es la misma que la dirección de la clase base. La herencia múltiple hace que eso sea falso.

¿La herencia virtual también hace que eso sea falso? En otras palabras, ¿es correcto el siguiente código?

struct A {};

struct B : virtual A
{
    int i;
};

int main()
{
    A* a = new B; // implicit upcast
    B* b = reinterpret_cast<B*>(a); // fishy?
    b->i = 0;

    return 0;
}
usuario1610015
fuente
1
reinterpret_castwith classes es siempre sospechoso (excepto de clase a void*, y de regreso a la misma clase).
Hyde
3
"Todos sabemos que cuando se usa una herencia simple simple, la dirección de una clase derivada es la misma que la dirección de la clase base" es una declaración bastante sólida. ¿Estás seguro de que el estándar garantiza esto?
hyde
77
Es una peculiaridad interesante de los lenguajes humanos que ninguna oración que comience con "todos sabemos eso" es verdadera.
molbdnilo
55
"C ++ sería un lenguaje poco práctico si solo confiamos en lo que garantiza el estándar" No estoy desarrollando C ++ durante décadas como otros podrían haberlo hecho, pero nunca tuve que violar el estándar o confiar en un comportamiento no especificado / indefinido en mis aplicaciones.
Timo
2
@ user1610015 Su primera oración no siempre es cierta con algunos compiladores importantes y no es así según el estándar C ++, por lo que debe haber alguna otra especificación (sobre un compilador particular o ABI) que la garantice para su caso particular.
Öö Tiib

Respuestas:

5

Todos sabemos que cuando se usa una herencia simple simple, la dirección de una clase derivada es la misma que la dirección de la clase base.

Creo que la afirmación no es cierta. En el siguiente código, tenemos una herencia simple (no virtual) simple (no múltiple), pero las direcciones son diferentes.

class A
{
public:
   int getX()
   {
      return 0;
   }
};

class B : public A
{
public:
   virtual int getY()
   {
      return 0;
   }
};

int main()
{
   B b;
   B* pB = &b;

   //A* pA = dynamic_cast<A*>(pB);
   A* pA = static_cast<A*>(pB);

   std::cout << "The address of pA is: " << pA << std::endl;
   std::cout << "The address of pB is: " << pB << std::endl;

   return 0;
}

y la salida para VS2015 es:

The address of pA is: 006FF8F0
The address of pB is: 006FF8EC

¿La herencia virtual también hace que eso sea falso?

Si cambia la herencia en el código anterior a virtual, el resultado será el mismo. así, incluso en el caso de la herencia virtual, las direcciones de los objetos base y derivados pueden ser diferentes.

Gupta
fuente
En realidad, g ++ también confirma su caso con un código de bit modificado: coliru.stacked-crooked.com/a/ccea741b7126ee8a
Öö Tiib
@ ÖöTiib void main()es aceptable incluso en los compiladores modernos de MSVS. Por cierto, gracias por el comentario. He actualizado el código.
Gupta
1
No, void main()no es aceptable Tiene que estar de int main()acuerdo con el estándar. Y elimine eso dynamic_castdel código, no es necesario allí y causa confusión.
geza
2

El resultado de reinterpret_cast<B*>(a);solamente está garantizada a punto a la envolvente Bobjeto de asi el asubobjeto y la envolvente Bobjeto son puntero-interconvertibles , ver [expr.static.cast] / 3 de la C ++ 17 estándar.

El objeto de clase derivado es interconvertible con el puntero con el objeto de clase base solo si el objeto derivado es un diseño estándar , no tiene miembros de datos directos no estáticos y el objeto de clase base es su primer subobjeto de clase base. [basic.compound] /4.3

Tener una virtualclase base descalifica a una clase de ser un diseño estándar . [clase] /7.2 .

Por lo tanto, debido a que Btiene una clase base virtual y un miembro de datos no estático, bno apuntará al Bobjeto que lo encierra , sino que bel valor del puntero permanecerá sin cambios con respecto aal de.

Acceder al imiembro como si estuviera apuntando al Bobjeto tiene un comportamiento indefinido.

Cualquier otra garantía provendría de su ABI específico u otra especificación.

nuez
fuente
2

La herencia múltiple hace que eso sea falso.

Eso no es del todo correcto. Considere este ejemplo:

struct A {};
struct B : A {};
struct C : A {};
struct D : B, C {};

Al crear una instancia de D, By Cse instancian cada uno con su instancia respectiva de A. Sin embargo, no habría ningún problema si la instancia de Dtuviera la misma dirección de su instancia de By su respectiva instancia de A. Aunque no es obligatorio, esto es exactamente lo que sucede al compilar clang 11y gcc 10:

D: 0x7fffe08b4758 // address of instance of D
B: 0x7fffe08b4758 and A: 0x7fffe08b4758 // same address for B and A
C: 0x7fffe08b4760 and A: 0x7fffe08b4760 // other address for C and A

¿La herencia virtual también hace que eso sea falso?

Consideremos una versión modificada del ejemplo anterior:

struct A {};
struct B : virtual A {};
struct C : virtual A {};
struct D : B, C {};

El uso del virtualespecificador de funciones se usa generalmente para evitar llamadas ambiguas a funciones. Por lo tanto, cuando se usa la virtualherencia, ambos By las Cinstancias deben crear una Ainstancia común . Al crear instancias D, obtenemos las siguientes direcciones:

D: 0x7ffc164eefd0
B: 0x7ffc164eefd0 and A: 0x7ffc164eefd0 // again, address of A and B = address of D
C: 0x7ffc164eefd8 and A: 0x7ffc164eefd0 // A has the same address as before (common instance)

¿Es correcto el siguiente código?

Aquí no hay ninguna razón para usar reinterpret_cast, aún más, resulta en un comportamiento indefinido. Use en su static_castlugar:

A* pA = static_cast<A*>(pB);

Ambos lanzamientos se comportan de manera diferente en este ejemplo. La reinterpret_castreinterpretará pBcomo un puntero a A, pero el puntero pApuede apuntar a una dirección diferente, como en el ejemplo anterior (C vs A). El puntero se subirá correctamente si lo usa static_cast.

mfnx
fuente
-2

La razón ay bson diferentes en su caso es porque, dado Aque no tiene ningún método virtual, Ano está manteniendo a vtable. Por otro lado, Bmantiene a vtable.

Cuando se sube a A, el compilador es lo suficientemente inteligente como para omitir lo vtableprevisto B. Y de ahí la diferencia en las direcciones. No deberías reinterpret_castvolver a Bhacerlo, no funcionaría.

Para verificar mi reclamo, intente agregar un virtualmétodo, digamos virtual void foo() {}en class A. Ahora Atambién mantendrá a vtable. Por lo tanto, downcast ( reinterpret_cast) a B le devolverá el original b.

theWiseBro
fuente
Las vtables no son relevantes aquí.
mfnx
La pregunta de @mfnx OP Subclass address equal to virtual base class address? tiene que ver con la herencia virtual. Y la herencia virtual tiene que ver con vtables.
theWiseBro
El ejemplo de @walnut OP realiza una herencia virtual. La razón por la que el lanzamiento arrojará un resultado incorrecto se debe a la ausencia de vtable en la clase A. Es cierto que esto no debe hacerse y es ilegal, pero bueno, seamos prácticos.
theWiseBro
@geza Sí, perdón por mi estúpido comentario. Aún así, dudo que agregar métodos virtuales Agarantice que las direcciones coincidan y que el elenco funcione, ya sea en teoría o en la práctica. No puedo eliminar mi voto negativo sin una edición de publicación, porque se ha bloqueado.
nogal
"La razón por la que a y b son diferentes en ...": pueden compartir la misma dirección. Tener una vtable o no no significa que tendrá las mismas direcciones que no. Tenga en cuenta las diferencias que obtuve con clang y gcc en comparación con @gupta con VS2015.
mfnx