malloc y la colocación de nuevos vs nueva


He estado investigando esto durante los últimos días, y hasta ahora no he encontrado nada convincente que no sean argumentos dogmáticos o apelaciones a la tradición (es decir, "¡es la manera de C++!").

Si estoy creando una matriz de objetos, ¿cuál es la razón convincente (aparte de la facilidad) para usar:

#define MY_ARRAY_SIZE 10

//  ...

my_object * my_array=new my_object [MY_ARRAY_SIZE];

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i]=my_object(i);

Sobre

#define MEMORY_ERROR -1
#define MY_ARRAY_SIZE 10

//  ...

my_object * my_array=(my_object *)malloc(sizeof(my_object)*MY_ARRAY_SIZE);
if (my_object==NULL) throw MEMORY_ERROR;

for (int i=0;i<MY_ARRAY_SIZE;++i) new (my_array+i) my_object (i);

Por lo que puedo decir, este último es mucho más eficiente que el primero (ya que no inicializa la memoria a algún valor/llamada no aleatorio constructores por defecto innecesariamente), y la única diferencia realmente es el hecho de que uno se limpia con:

delete [] my_array;

Y el otro que limpiar con:

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i].~T();

free(my_array);

Estoy fuera por una convincente razón. Apelaciones al hecho de que es C++ (no C) y por lo tanto malloc y free no debe ser utilizado no, como lo que puedo decir -- convincente, como es de dogmática. ¿Hay algo que me falta que haga new [] superior a malloc?

I es decir, lo mejor que puedo decir, ni siquiera se puede usar new []} en absoluto.para hacer una matriz de cosas que no tienen un constructor predeterminado, sin parámetros, mientras que el método malloc se puede usar así.

Author: BlueRaja - Danny Pflughoeft, 2012-01-22

11 answers

Si no desea que su memoria se inicialice mediante llamadas de constructores implícitas, y solo necesita una asignación de memoria asegurada para placement new, entonces está perfectamente bien usar malloc y free en lugar de new[] y delete[].

Las razones convincentes de usar new sobre malloc es que new proporciona inicialización implícita a través de llamadas a constructores, ahorrándole memset adicionales o llamadas a funciones relacionadas post an malloc Y que para new no necesita verificar {[11]]} después de cada asignación, simplemente encerrando los manejadores de excepciones hará el trabajo ahorrándole la comprobación de errores redundante a diferencia de malloc.
Estas dos razones de peso no se aplican a su uso.

Cuál es el rendimiento eficiente solo se puede determinar mediante perfiles, no hay nada malo en el enfoque que tiene ahora. En una nota al margen, tampoco veo una razón convincente de por qué usar malloc sobre new[].

 35
Author: Alok Save,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-22 07:25:52

Estoy fuera por una razón convincente.

Depende de cómo definas "convincente". Muchos de los argumentos que ha rechazado hasta ahora son ciertamente convincentes para la mayoría de los programadores de C++, ya que su sugerencia no es la forma estándar de asignar matrices desnudas en C++.

El simple hecho es este: sí, absolutamente puedes hacer las cosas de la manera que describes. No hay razón para que lo que estás describiendo no funcione .

Pero de nuevo, puedes tiene funciones virtuales en C. Puede implementar clases y herencia en C simple, si pone el tiempo y el esfuerzo en ello. Esos también son completamente funcionales.

Por lo tanto, lo que importa no es si algo puede funcionar. Pero más sobre lo que cuesta son. Es mucho más propenso a errores implementar funciones virtuales y de herencia en C que en C++. Hay múltiples formas de implementarlo en C, lo que conduce a implementaciones incompatibles. Mientras que, porque son características de lenguaje de primera clase de C++, es muy poco probable que alguien implementaría manualmente lo que ofrece el lenguaje. Por lo tanto, la herencia y las funciones virtuales de todos pueden cooperar con las reglas de C++.

Lo mismo ocurre con esto. Entonces, ¿cuáles son las ganancias y las pérdidas de la gestión manual de malloc/matriz libre?

No puedo decir que nada de lo que estoy a punto de decir constituya una "razón convincente" para ti. Dudo que lo haga, ya que parece que ha tomado una decisión. Pero para que conste:

Rendimiento

Usted afirma lo siguiente:

Por lo que puedo decir, este último es mucho más eficiente que el primero (ya que no inicializa la memoria a algún valor no aleatorio/llama a constructores predeterminados innecesariamente), y la única diferencia realmente es el hecho de que uno se limpia con:

Esta afirmación sugiere que la ganancia de eficiencia está principalmente en la construcción de los objetos en cuestión. Es decir, que se llama a los constructores. La instrucción presupone que no quiere llamar al constructor predeterminado; que use un constructor predeterminado solo para crear la matriz, luego use la función de inicialización real para poner los datos reales en el objeto.

Bueno... ¿y si eso no es lo que quieres hacer? ¿Qué pasa si lo que quieres hacer es crear un array vacío, uno que está construido por defecto? En este caso, esta ventaja desaparece completamente.

Fragilidad

Supongamos que cada objeto en la matriz necesita tener un constructor especializado o algo llamado en él, de modo que la inicialización de la matriz requiere este tipo de cosas. Pero considere su código de destrucción:

for (int i=0;i<MY_ARRAY_SIZE;++i) my_array[i].~T();

Para un caso simple, esto está bien. Tienes una variable macro o const que dice cuántos objetos tienes. Y recorrer cada elemento para destruir los datos. Eso es genial para un ejemplo simple.

Ahora considere un verdadero aplicación, no es un ejemplo. ¿En cuántos lugares diferentes crearás una matriz? Docenas? Cientos? Todos y cada uno necesitarán tener su propio bucle for para inicializar el array. Todos y cada uno necesitarán tener su propio bucle for para destruir la matriz.

Escriba mal esto incluso una vez, y puede dañar la memoria. O no borrar algo. O cualquier cantidad de otras cosas horribles.

Y aquí hay una pregunta importante: para una matriz dada, ¿dónde ¿mantener el tamaño ? ¿Sabes cuántos elementos asignaste para cada matriz que creas? Cada matriz probablemente tendrá su propia manera de saber cuántos artículos almacena. Así que cada bucle destructor tendrá que obtener estos datos correctamente. Si se equivoca... auge.

Y luego tenemos la seguridad de excepción, que es toda una nueva lata de gusanos. Si uno de los constructores lanza una excepción, los objetos construidos previamente deben ser destruidos. Tu código no hace eso; no es excepción-seguro.

Ahora, considere la alternativa:

delete[] my_array;

Esto no puede fallar. Siempre destruirá todos los elementos. Rastrea el tamaño de la matriz, y es seguro para excepciones. Así que está garantizado para trabajar. No puede no trabajar (siempre y cuando usted lo asignó con new[]).

Por supuesto, se podría decir que se podría envolver la matriz en un objeto. Eso tiene sentido. Incluso puede templar el objeto en los elementos de tipo de la matriz. De esa manera, todos los el código de desturctor es el mismo. El tamaño está contenido en el objeto. Y tal vez, solo tal vez, te das cuenta de que el usuario debe tener cierto control sobre la forma particular en que se asigna la memoria, para que no sea solo malloc/free.

Felicitaciones: acabas de reinventarte std::vector.

Es por eso que muchos programadores de C++ ya ni siquiera escriben new[].

Flexibilidad

Tu código usa malloc/free. Pero digamos que estoy haciendo algunos perfiles. Y me doy cuenta de que malloc/free para ciertos tipos creados con frecuencia es demasiado caro. Creo un gestor de memoria especial para ellos. Pero ¿cómo enganchar todas las asignaciones de matriz a ellos?

Bueno, tengo que buscar en la base de código cualquier ubicación donde cree/destruya matrices de este tipo. Y luego tengo que cambiar sus asignadores de memoria en consecuencia. Y luego tengo que observar continuamente la base de código para que alguien más no cambie esos asignadores o introduzca un nuevo código de matriz que use diferentes asignadores.

Si estuviera usando new[]/delete[], podría usar la sobrecarga del operador. Simplemente proporciono una sobrecarga para los operadores new[] y delete[] para esos tipos. Ningún código tiene que cambiar. Es mucho más difícil para alguien eludir estas sobrecargas; tienen que intentarlo activamente. Y así sucesivamente.

Así que obtengo una mayor flexibilidad y una garantía razonable de que mis asignadores se usarán donde se deben usar.

Legibilidad

Considere esto:

my_object *my_array = new my_object[10];
for (int i=0; i<MY_ARRAY_SIZE; ++i)
  my_array[i]=my_object(i);

//... Do stuff with the array

delete [] my_array;

Compáralo con esto: {[34]]}

my_object *my_array = (my_object *)malloc(sizeof(my_object) * MY_ARRAY_SIZE);
if(my_object==NULL)
  throw MEMORY_ERROR;

int i;
try
{
    for(i=0; i<MY_ARRAY_SIZE; ++i)
      new(my_array+i) my_object(i);
}
catch(...)  //Exception safety.
{
    for(i; i>0; --i)  //The i-th object was not successfully constructed
        my_array[i-1].~T();
    throw;
}

//... Do stuff with the array

for(int i=MY_ARRAY_SIZE; i>=0; --i)
  my_array[i].~T();
free(my_array);

Objetivamente hablando, ¿cuál de estos es más fácil de leer y entender lo que está pasando?

Basta con mirar esta declaración: (my_object *)malloc(sizeof(my_object) * MY_ARRAY_SIZE). Esto es un muy cosa de bajo nivel. No estás asignando una matriz de nada; estás asignando un trozo de memoria. Debe calcular manualmente el tamaño del trozo de memoria para que coincida con el tamaño del objeto * el número de objetos que desea. Incluso cuenta con un molde.

Por contraste, new my_object[10] cuenta la historia. new es la palabra clave de C++ para "crear instancias de tipos". my_object[10] es una matriz de 10 elementos de tipo my_object. Es simple, obvio e intuitivo. No hay casting, no hay computación de tamaños de bytes, nada.

El método malloc requiere aprender cómo usar malloc idiomáticamente. El método new requiere simplemente entender cómo funciona new. Es mucho menos detallado y mucho más obvio lo que está pasando.

Además, después de la malloc declaración, de hecho no tiene una matriz de objetos. malloc simplemente devuelve un bloque de memoria que le ha dicho al compilador de C++ que pretenda que es un puntero a un objeto (con un cast). no es una matriz de objetos, porque los objetos en C++ tienen vidas. Y la vida de un objeto no comienza hasta que se construye. Nada en esa memoria ha tenido un constructor llamado a ella todavía, y por lo tanto no hay objetos vivos en ella.

my_array en ese punto no es una matriz; es sólo un bloque de memoria. No se convierte en un array de my_objects hasta que los construyes en el siguiente paso. Esto es increíblemente poco intuitivo para un programador nuevo; se necesita una mano experimentada de C++ (alguien que probablemente aprendió de C) para saber que esos no son objetos vivos y deben tratarse con cuidado. El puntero todavía no se comporta como un my_object* apropiado, porque todavía no apunta a ningún my_object.

Por el contrario, usted tiene objetos vivos en el caso new[]. Los objetos han sido construidos; están vivos y totalmente formados. Puede usar este puntero como cualquier otro my_object*.

Fin

Nada de lo anterior dice que este mecanismo no sea potencialmente útil en las circunstancias correctas. Pero una cosa es reconocer la utilidad de algo en ciertas circunstancias. Es muy diferente decir que debería ser la forma predeterminada de hacer las cosas.

 63
Author: Nicol Bolas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-22 20:04:47

Yo diría que ninguna.

La mejor manera de hacerlo sería:

std::vector<my_object>   my_array;
my_array.reserve(MY_ARRAY_SIZE);

for (int i=0;i<MY_ARRAY_SIZE;++i)
{    my_array.push_back(my_object(i));
}

Esto se debe a que internamente vector probablemente está haciendo la colocación nueva para usted. También administra todos los demás problemas asociados con la administración de memoria que no está teniendo en cuenta.

 18
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-22 07:56:19

Has reimplementado new[]/delete[] aquí, y lo que has escrito es bastante común en el desarrollo de asignadores especializados.

La sobrecarga de llamar a constructores simples tomará poco tiempo en comparación con la asignación. No es necesariamente 'mucho más eficiente' depends depende de la complejidad del constructor por defecto, y de operator=.

Una cosa buena que no se ha mencionado todavía es que el tamaño de la matriz se conoce por new[]/delete[]. delete[] solo hace lo correcto y destruye todo elementos cuando se le pregunta. Arrastrar una variable adicional (o tres) alrededor para que exactamente cómo destruir la matriz es un dolor. Un tipo de colección dedicada sería una buena alternativa, sin embargo.

new[]/delete[] son preferibles por conveniencia. Introducen poca sobrecarga, y podrían salvarte de muchos errores tontos. ¿Está lo suficientemente obligado a quitar esta funcionalidad y usar una colección/contenedor en todas partes para apoyar su construcción personalizada? He implementado este asignador the el real mess está creando funtores para todas las variaciones de construcción que necesita en la práctica. En cualquier caso, a menudo tiene una ejecución más exacta a expensas de un programa que a menudo es más difícil de mantener que los modismos que todo el mundo conoce.

 9
Author: justin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-22 10:32:38

EN mi humilde opinión hay tanto feo, es mejor utilizar vectores. Solo asegúrese de asignar el espacio de antemano para el rendimiento.

O bien:

std::vector<my_object> my_array(MY_ARRAY_SIZE);

Si desea inicializar con un valor predeterminado para todas las entradas.

my_object basic;
std::vector<my_object> my_array(MY_ARRAY_SIZE, basic);

O si no quieres construir los objetos pero quieres reservar el espacio:

std::vector<my_object> my_array;
my_array.reserve(MY_ARRAY_SIZE);

Entonces si necesita acceder a él como una matriz de puntero de estilo C simplemente (solo asegúrese de no agregar cosas mientras mantiene el puntero antiguo, pero no podría hacer eso con matrices normales de estilo c de todos modos.)

my_object* carray = &my_array[0];      
my_object* carray = &my_array.front(); // Or the C++ way

Acceder a elementos individuales:

my_object value = my_array[i];    // The non-safe c-like faster way
my_object value = my_array.at(i); // With bounds checking, throws range exception

Typedef for pretty:

typedef std::vector<my_object> object_vect;

Pasarlas por funciones con referencias:

void some_function(const object_vect& my_array);

EDITAR: EN C++11 también existe std::array. Sin embargo, el problema es que su tamaño se realiza a través de una plantilla, por lo que no puede hacer diferentes tamaños en tiempo de ejecución y no puede pasarlo a funciones a menos que esperen exactamente el mismo tamaño (o sean funciones de plantilla en sí mismas). Pero puede ser útil para cosas como buffers.

std::array<int, 1024> my_array;

EDIT2: También en C++11 hay un nuevo emplace_back como alternativa a push_back. Esto básicamente le permite 'mover' su objeto (o construir su objeto directamente en el vector) y le guarda una copia.

std::vector<SomeClass> v;
SomeClass bob {"Bob", "Ross", 10.34f};
v.emplace_back(bob);
v.emplace_back("Another", "One", 111.0f); // <- Note this doesn't work with initialization lists ☹
 6
Author: David C. Bishop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-20 02:48:00

Oh bien, estaba pensando que dado el número de respuestas no habría razón para intervenir... pero supongo que me siento atraído como los demás. Vamos

  1. Por qué su solución está rota
  2. C++11 nuevas facilidades para manejar memoria raw
  3. Una forma más sencilla de hacer esto
  4. Consejos

1. Por qué su solución está rota

Primero, los dos fragmentos que presentó no son equivalentes. new[] solo funciona, el tuyo falla horriblemente en presencia de Excepciones.

Lo que new[] hace bajo la cubierta es que mantiene un registro del número de objetos que se construyeron, de modo que si ocurre una excepción durante digamos que el 3er constructor lo llama correctamente llama al destructor para los 2 objetos ya construidos.

Su solución sin embargo falla horriblemente:

  • o bien no maneja excepciones en absoluto (y filtra horriblemente)
  • o simplemente tratar de llamar a los destructores en toda la matriz a pesar de que está a medio construir (probablemente estrellarse, pero quién sabe con un comportamiento indefinido)

Así que los dos claramente no son equivalentes. El tuyo está roto

2. C++11 nuevas facilidades para manejar memoria raw

En C++11, los miembros del comité se han dado cuenta de lo mucho que nos gustaba jugar con la memoria cruda y han introducido instalaciones para ayudarnos a hacerlo de manera más eficiente y segura.

Compruebe cppreference <memory> breve. Este ejemplo muestra las nuevas golosinas ( * ):

#include <iostream>
#include <string>
#include <memory>
#include <algorithm>

int main()
{
    const std::string s[] = {"This", "is", "a", "test", "."};
    std::string* p = std::get_temporary_buffer<std::string>(5).first;

    std::copy(std::begin(s), std::end(s),
              std::raw_storage_iterator<std::string*, std::string>(p));

    for(std::string* i = p; i!=p+5; ++i) {
        std::cout << *i << '\n';
        i->~basic_string<char>();
    }
    std::return_temporary_buffer(p);
}

Tenga en cuenta que get_temporary_buffer es no-throw, devuelve el número de elementos para los que la memoria se ha asignado realmente como un segundo miembro del pair (por lo tanto, el .first para obtener el puntero).

(*) O tal vez no tan nuevo como MooingDuck comentó.

3. Una forma más sencilla de hacerlo

En lo que a mí respecta, lo que realmente parece estar pidiendo es una especie de memory pool, donde algunos emplazamientos podrían no haber sido inicializados.

¿Sabes sobre boost::optional ?

Es básicamente un área de memoria raw que puede ajustarse a un elemento de un tipo dado (parámetro de plantilla), pero por defecto no tiene nada en su lugar. Tiene una interfaz similar a un puntero y le permite consultar si la memoria está realmente ocupada o no. Finalmente, usando las Fábricas In Situ puede usarlo de forma segura sin copiar objetos si es preocupación.

Bueno, su caso de uso realmente se parece a un std::vector< boost::optional<T> > para mí (o tal vez un deque?)

4. Consejos

Finalmente, en caso de que realmente quiera hacerlo por su cuenta, ya sea para aprender o porque ningún contenedor STL realmente le conviene, le sugiero que envuelva esto en un objeto para evitar que el código se extienda por todo el lugar.

No olvides: ¡No Te Repitas!

Con un objeto (templada) se puede capturar la esencia de su diseño en un solo lugar, y luego reutilizarlo en todas partes.

Y, por supuesto, ¿por qué no aprovechar las nuevas instalaciones de C++11 mientras lo hace :) ?

 5
Author: Matthieu M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-23 07:45:38

Debe usar vectors.

 3
Author: ddacot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-22 10:55:23

Dogmático o no, eso es exactamente lo que TODOS los contenedores STL hacen para asignar e inicializar.

Usan un asignador luego asigna espacio no inicializado e inicializarlo por medio de los constructores de contenedores.

Si esto (como muchas personas utilizan para decir) "¿no es c++" cómo puede ser que la biblioteca estándar se implemente así?

Si simplemente no desea usar malloc / free, puede asignar "bytes" con solo new char[]

myobjet* pvext = reinterpret_cast<myobject*>(new char[sizeof(myobject)*vectsize]);
for(int i=0; i<vectsize; ++i) new(myobject+i)myobject(params);
...
for(int i=vectsize-1; i!=0u-1; --i) (myobject+i)->~myobject();
delete[] reinterpret_cast<char*>(myobject);

Esto permite usted aprovecha la separación entre inicialización y asignación, aún tomando adwantage del mecanismo de excepción de asignación new.

Tenga en cuenta que, poniendo mi primera y última línea en una clase myallocator<myobject> y el segundo ands penúltimo en una clase myvector<myobject>, tenemos ... solo reimplementado std::vector<myobject, std::allocator<myobject> >

 2
Author: Emilio Garavaglia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-22 10:29:42

Lo que ha mostrado aquí es realmente el camino a seguir cuando se usa un asignador de memoria diferente al asignador general del sistema - en ese caso, asignaría su memoria usando el asignador (alloc->malloc(sizeof(my_object))) y luego usaría el operador placement new para inicializarlo. Esto tiene muchas ventajas en la gestión eficiente de la memoria y es bastante común en la biblioteca de plantillas estándar.

 1
Author: smichak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-22 07:34:34

Si está escribiendo una clase que imita la funcionalidad de std::vector o necesita control sobre la asignación de memoria/creación de objetos (inserción en matriz / eliminación, etc.- ese es el camino a seguir. En este caso, no se trata de "no llamar al constructor predeterminado". Se convierte en una cuestión de ser capaz de" asignar memoria cruda, memmove objetos antiguos allí y luego crear nuevos objetos en las direcciones de los antiguos", cuestión de ser capaz de utilizar alguna forma de realloc y así sucesivamente. Sin duda, asignación personalizada + la colocación new es mucho más flexible... Lo sé, estoy un poco borracho, pero es para maricas... Acerca de la eficiencia-uno puede escribir su propia versión de std::vector que será AL MENOS tan rápida ( y probablemente más pequeña, en términos de sizeof()) con la funcionalidad más utilizada del 80% de std::vector en, probablemente, menos de 3 horas.

 1
Author: lapk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-22 09:00:17
my_object * my_array=new my_object [10];

Este será un array con objetos.

my_object * my_array=(my_object *)malloc(sizeof(my_object)*MY_ARRAY_SIZE);

Este será un array del tamaño de tus objetos, pero pueden estar "rotos". Si su clase tiene funciones virtuales, por ejemplo, entonces no podrá llamarlas. Tenga en cuenta que no son solo sus datos de miembro los que pueden ser inconsistentes, sino que todo el objeto está actully "roto" (a falta de una palabra mejor)

No estoy diciendo que esté mal hacer la segunda, siempre y cuando sepas esto.

 0
Author: user1130005,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-22 07:37:07