2011-02-05 13 views
6

Tengo esta jerarquía de clases trivial:En C++, ¿por qué se necesita `nuevo` para crear dinámicamente un objeto en lugar de asignación?

class Base { 
public: 
    virtual int x() const = 0; 
}; 

class Derived : public Base { 
    int _x; 
public: 
    Derived(int x) : _x(x) { } 
    int x() const { return _x; } 
}; 

Si uso malloc asignar una instancia de Derived, y luego tratar de acceder a la función polimórfica x, errores en el programa (me da un fallo de segmentación):

int main() { 
    Derived *d; 
    d = (Derived*) malloc(sizeof(Derived)); 
    *d = Derived(123); 

    std::cout << d->x() << std::endl; // crash 

    return 0; 
} 

Por supuesto, mi aplicación real es mucho más compleja (es una especie de grupo de memoria).


estoy bastante seguro de que es debido a la forma asigno d: Yo no utilizar new.

sé de placement new operador, que debe ser lo que necesito, pero nunca he utilizado y han conseguido algunas preguntas:

  • ¿Por qué mi estrellarse aplicación, si yo no uso new ?

    ¿Qué hace realmente new?

    ¿Por qué no puedo simplemente usar el operador de asignación para asignar el valor de Derived(123); al área de memoria apuntada por d?

  • ¿Tendría que usar new también para tipos no polimórficos?

    ¿Qué hay de los POD?

  • En el C++Faq I linked above dice que la región de memoria pasada a la ubicación new debe estar alineada para el objeto que estoy creando.

    Sé lo que es la alineación, pero no sé cómo verificar la alineación necesaria para mi clase.

    malloc manual dice:

    El malloc() y calloc() devuelven un puntero a la memoria asignada que está convenientemente preparada para cualquier tipo de variable.

    y espero que la alineación sea necesario para mi clase es el tamaño de la clase que devuelve sizeof, por lo que cualquier dirección en forma address_returned_by_malloc + i * sizeof(my_class) es adecuado para asignar mis objetos.

    ¿Tengo esperanzas?

+0

¿Por qué no acaba de anular el nuevo operador para esta clase? – Earlz

+0

"¿Por qué' malloc' no funciona para las clases? " y "¿Por qué necesitamos' nuevo'? " son preguntas muy diferentes, una de las cuales tiene una respuesta. –

+0

@Earlz, estoy a punto de ** cargar ** nuevo operador, pero quería saber por qué tenía que usar 'new'. @Chris Lutz: sí y no; esta pregunta fue: "¿Por qué necesito usar' new' en lugar de 'malloc' para crear instancias de objetos de clase?" – peoro

Respuesta

3

Bajemos la línea

  1. por qué es mi estrellarse aplicación, si yo no uso nuevo?

La tabla virtual está dañada.

La tabla virtual está bloqueada justo después de la memoria asignada. cuando new una clase, el código generado configurará correctamente el vtable. Sin embargo, malloc no inicializar correctamente la viable

Para ver la tabla virtual, ejecute g ++ -fdump clase jerarquía

Vtable for Derived 
Derived::_ZTV7Derived: 3u entries 
0  (int (*)(...))0 
8  (int (*)(...))(& _ZTI7Derived) 
16 Derived::x 

Class Derived 
    size=16 align=8 
    base size=12 base align=8 
Derived (0x10209fc40) 0 
    vptr=((& Derived::_ZTV7Derived) + 16u) <-- notice how this is part of the structure 
    Base (0x10209fcb0) 0 nearly-empty 
     primary-for Derived (0x10209fc40) 

Por una razón similar, sin la sobrecarga de operadores =, el código ensamblador generado se Sólo copiar los datos y no la viable [de nuevo, el compilador sólo sabe para copiar los datos, no la viable]

Si desea ver una versión basada en puntero vtable con una función válida:

Derived e(123); 
d = &e; 
  1. ¿Tendría que usar nuevo también para tipos no polimórficos?

Si está utilizando las funciones virtuales, entonces sí, incluso para los tipos no polimórficos

  1. espero que la alineación sea necesario para mi clase es el tamaño de la clase que devuelve sizeof, por lo que cualquier dirección en la forma address_returned_by_malloc + i * sizeof (my_class) es adecuado para asignar mis objetos.

La alineación no es un problema.

+2

Sizeof no omite el tamaño de vtable. El tamaño de vtable está incluido en el tamaño de, no hay problema. el problema es simplemente que malloc no inicializa el vtable (o cualquier otra cosa). Si el OP utilizó la ubicación nueva en los datos malloc, no hay problema – bdonlan

+0

"sizeof salta intencionalmente el tamaño de vtable" no es correcto. –

+0

corrigió esto ... –

3

Debido malloc no llama al constructor de la clase, y no se sabe nada acerca de cualquier requisito de alineación particular que pueda tener. Si necesita utilizar malloc (no recomendado), consulte placement new (suponiendo que no desea sobrecargar el new normal por algún motivo).

+0

Esto no responde ninguna de mis tres preguntas. 'malloc' no llama al constructor, pero utilicé el operador de asignación, con un objeto correctamente construido en esa región de memoria, ¿por qué no es suficiente? – peoro

+0

No estoy seguro de si el operador de asignación realiza algún control de cordura (por ejemplo, ¿escribe cheques?) En el cesionario ... si lo hace, no funcionará. ¿Qué tipo de violación de acceso ve - leer o escribir? ¿Y dónde exactamente sucede en el código? – Mehrdad

+0

@ peoro: Las condiciones previas en el operador de asignación son que AMBOS lados son objetos construidos correctamente. –

1

No creo que se llame al constructor del objeto cuando usa malloc.

+0

¿Crees que de alguna manera mágicamente se llama el ctor? – Tim

+0

Tenía la impresión de que el nuevo operador llama al constructor. MSDN y Wikipedia parecen estar de acuerdo. ¿Puedes elaborar? – alanp

+2

No entiendo la continuación de la votación negativa aquí. alanp arregló el doble negativo en su respuesta, ahora es correcto. –

1

sección [basic.life] de la norma dice

El tiempo de vida de un objeto es una propiedad de tiempo de ejecución del objeto. Se dice que un objeto tiene una inicialización no trivial si es de una clase o un tipo agregado y uno de sus miembros es inicializado por un constructor que no sea un constructor trivial predeterminado. [Nota: la inicialización por un constructor de copia/movimiento trivial es una inicialización no trivial. - nota final] El tiempo de vida de un objeto de tipo T se inicia cuando: se obtiene

  • almacenamiento con la alineación apropiada y el tamaño para el tipo T, y
  • si el objeto tiene inicialización no trivial, su inicialización es completar.

Dado que la clase tiene miembros virtuales, se requiere inicialización no trivial. No puede asignar un objeto cuya vida útil no se haya iniciado, tiene que inicializarlo con new.

2

Las clases con virtual miembros contienen un puntero al llamado vtable, básicamente una tabla de indicadores de función para la implementación de estos miembros virtuales.Cuando utiliza operator new, se llama al constructor, que, incluso si es un constructor implícito, configurará este puntero al vtable correctamente.

Sin embargo, malloc no llama al constructor. El puntero vtable se deja sin inicializar, apunta a alguna memoria aleatoria. Cuando intenta llamar a una función virtual, desreferencia un puntero malo y se bloquea (comportamiento indefinido).

La solución es utilizar la colocación de nuevo para inicializar el objeto antes de utilizarlo:

int main() { 
    Derived *d; 
    d = (Derived*) malloc(sizeof(Derived)); 
    new(d) Derived(123); // invoke constructor 
// You could also do: 
// new(d) Derived; 
// *d = Derived(123); 

    std::cout << d->x() << std::endl; // crash 

    // Although in your case it does not matter, it's good to clean up after yourself by 
    // calling the destructor 
    d->~Derived(); 
    return 0; 
} 

Algunas cosas importantes a tener en cuenta:

  • alineación no es un problema. La memoria de malloc está alineada correctamente para cualquier tipo de C++.
  • Asignar con = no ayuda. La implementación predeterminada de = copia todas las variables miembro, pero el puntero vtable no es miembro y no se copia.
  • No se requiere la construcción para los tipos de POD. Los tipos que no son POD pueden o no requerirlo (es un comportamiento indefinido si no lo hace). En particular, el constructor también llama a los constructores de variables miembro; entonces si no construyes el objeto externo, los objetos internos también pueden romperse.
+3

Todas estas cosas de la tabla v son ciertas en la mayoría de las implementaciones, pero no son requeridas por la norma. Todo lo que dice el estándar es que los objetos con miembros virtuales no son POD y, por lo tanto, deben inicializarse ejecutando el constructor. –

Cuestiones relacionadas