2012-06-15 12 views
7

Sé que el diseño de memoria de la herencia múltiple no está definido, por lo que no debería confiar en él. Sin embargo, ¿puedo confiar en un caso especial? Es decir, una clase tiene una sola superclase "real". Todos los demás son "clases vacías", es decir, clases que no tienen campos ni métodos virtuales (es decir, solo tienen métodos no virtuales). En este caso, estas clases adicionales no deberían agregar nada al diseño de la memoria de la clase. (Más concisamente, en la redacción de C++ 11, la clase tiene diseño estándar)C++ Diseño de memoria de herencia múltiple con "Clases vacías"

¿Puedo inferir que todas las superclases no tendrán desplazamiento? Ej .:

#include <iostream> 

class X{ 

    int a; 
    int b; 
}; 

class I{}; 

class J{}; 

class Y : public I, public X, public J{}; 

int main(){ 

    Y* y = new Y(); 
    X* x = y; 
    I* i = y; 
    J* j = y; 

    std::cout << sizeof(Y) << std::endl 
        << y << std::endl 
        << x << std::endl 
        << i << std::endl 
        << j << std::endl; 
} 

Aquí, Y es la clase con X siendo la única clase base real. La salida del programa (cuando se compila en Linux con g ++ 4,6) es como sigue:

0x233f010

0x233f010

0x233f010

0x233f010

Como llegué a la conclusión, no hay poi nter ajuste. Pero esta implementación es específica o puedo confiar en ella. Es decir, si recibo un objeto del tipo I (y sé que solo existen estas clases), ¿puedo usar un reinterpret_cast para convertirlo al X?

Espero que pueda confiar en él porque la especificación dice que el tamaño de un objeto debe ser al menos un byte. Por lo tanto, el compilador no puede elegir otro diseño. Si tuviera el formato I y J detrás de los miembros de X, entonces su tamaño sería cero (porque no tienen miembros). Por lo tanto, la única opción razonable es alinear todas las súper clases sin compensación.

¿Estoy correcto o estoy jugando con el fuego si uso reinterpret_cast desde I hasta X aquí?

+3

El diseño de la memoria de la herencia, uno o varios, bases vacías o no, no está definido. –

+1

Diría que estás jugando con fuego, porque estás tratando de crear un constructo muy extraño que sin duda causará dolor y sufrimiento a cualquiera que tenga que entenderlo en el futuro, incluso si no se rompe cuando actualizas tu compilador! – Rook

+0

afortunadamente, sus declaraciones ya no son correctas en C++ 11, vea la respuesta :) – gexicide

Respuesta

8

En C++ 11 se requiere que el compilador utilice la Optimización de clase base vacía para tipos de disposición estándar. ver https://stackoverflow.com/a/10789707/981959

Para su ejemplo específico todos los tipos son clases de diseño estándar y no tienen clases base común o miembros (véase más adelante) para que pueda confiar en que el comportamiento en C++ 11 (y en la práctica, Creo que muchos compiladores ya siguieron esa regla, ciertamente G ++ lo hizo, y otros siguiendo el Itanium C++ ABI.)

Advertencia: asegúrese de no tener ninguna clase base del mismo tipo, porque deben estar en distintas direcciones, p.ej

struct I {}; 

struct J : I {}; 
struct K : I { }; 

struct X { int i; }; 

struct Y : J, K, X { }; 

#include <iostream> 

Y y; 

int main() 
{ 
    std::cout << &y << ' ' << &y.i << ' ' << (X*)&y << ' ' << (I*)(J*)&y << ' ' << (I*)(K*)&y << '\n'; 

} 

grabados:

0x600d60 0x600d60 0x600d60 0x600d60 0x600d61 

Para el tipo Y sólo una de las I bases pueden ser en el offset de cero, por lo que aunque la X sub-objeto es en el desplazamiento cero (es decir,offsetof(Y, i) es cero) y una de las bases I está en la misma dirección, pero la otra base I es (al menos con G ++ y Clang ++) un byte en el objeto, por lo que si obtuvo I* no pudo reinterpret_cast a porque no vas a saber lo que I subobjeto se señaló, el I en la posición 0 o el 1. I en el desplazamiento

está bien para el compilador para colocar la segunda I sub-objeto en la posición 1 (es decir, dentro deint) porque I no tiene miembros de datos no estáticos, por lo que no puede reall y desreferencia o acceso a cualquier cosa en esa dirección, solo obtenga un puntero al objeto en esa dirección. Si agregó miembros de datos no estáticos al I, entonces Y ya no sería un diseño estándar y no tendría que usar el EBO, y offsetof(Y, i) ya no sería cero.

+0

perfecto, por lo que funciona :) – gexicide

+0

tenga en cuenta la advertencia importante que amplié en mi edición aunque –

+0

sí, por supuesto. Debo asegurarme de que la clase tenga un diseño estándar. ¡Gracias! – gexicide