¿Están 'nuevo' y 'eliminar' en desuso en C ++?

68

Me topé con un cuestionario que involucraba una declaración de matriz con diferentes tamaños. Lo primero que me vino a la mente es que necesitaría usar una asignación dinámica con el newcomando, así:

while(T--) {
   int N;
   cin >> N;
   int *array = new int[N];
   // Do something with 'array'
   delete[] array;
}

Sin embargo, vi que una de las soluciones permitía el siguiente caso:

while(T--) {
    int N;
    cin >> N;
    int array[N];
    // Do something with 'array'
}

Después de un poco de investigación, leí que g ++ permite esto, pero me mantuvo pensando, ¿en qué casos es necesario usar la asignación dinámica? ¿O es que el compilador traduce esto como asignación dinámica?

La función de eliminación está incluida. Sin embargo, tenga en cuenta que la pregunta aquí no se trata de pérdidas de memoria.

aprendizaje_dude
fuente
54
El segundo ejemplo usa una matriz de longitud variable que nunca ha sido parte de C ++. Para este caso, use std::vectoren su lugar ( std::vector<int> array(N);).
Algún tipo programador el
77
La respuesta directa a su pregunta debería ser: no, no se está desaprobando. Aunque las versiones modernas de C ++ proporcionan muchas características que simplifican la administración de la propiedad de la memoria (punteros inteligentes), sigue siendo una práctica común asignar objetos invocando new OBJdirectamente.
pptaszni
8
Para otras personas que están confundidas acerca de por qué las personas hablan de pérdidas de memoria, la pregunta se editó para corregir un error que no era material para la pregunta
Mike Caron,
44
@Mannoj prefiere usar los términos Dinámico y Automático para apilar y apilar. Es raro, pero es posible implementar C ++ sin montones y pilas.
user4581301
1
Nada ha quedado en desuso en C ++ y nada lo hará. Eso es parte de lo que significa C ++.
JoelFan

Respuestas:

114

Ninguno de los fragmentos que muestra es un código C ++ idiomático y moderno.

newy delete(y new[]y delete[]) no están en desuso en C ++ y nunca lo estarán. Todavía son la forma de instanciar objetos asignados dinámicamente. Sin embargo, como siempre debe hacer coincidir a newcon a delete(y a new[]con a delete[]), es mejor guardarlos dentro de las clases (de la biblioteca) que garanticen esto. Consulte ¿Por qué los programadores de C ++ deberían minimizar el uso de 'nuevo'? .

Su primer fragmento utiliza un "desnudo" new[]y luego nunca delete[]es la matriz creada. Eso es un problema. std::vectorhace todo lo que necesitas aquí bien. Utilizará alguna forma de newdetrás de escena (no me sumergiré en los detalles de implementación), pero por lo que debe preocuparse, es una matriz dinámica pero mejor y más segura.

Su segundo fragmento utiliza "matrices de longitud variable" (VLA), una característica de C que algunos compiladores también permiten en C ++ como una extensión. A diferencia new, los VLA se asignan esencialmente en la pila (un recurso muy limitado). Pero lo más importante, no son una característica estándar de C ++ y deben evitarse porque no son portátiles. Ciertamente no reemplazan la asignación dinámica (es decir, el montón).

Max Langhof
fuente
3
Me gustaría agregar que aunque los VLA no están oficialmente en el estándar, son compatibles con todos los compiladores principales, y por lo tanto la decisión de evitarlos es más una cuestión de estilo / preferencia que una preocupación realista por la portabilidad.
Stack Tracer
44
También recuerde que no puede devolver una matriz o almacenarla en otro lugar, por lo que VLA nunca sobrevivirá al tiempo de ejecución de la función
Ruslan
16
@StackTracer A mi leal saber y entender, MSVC no es compatible con VLA. Y MSVC es definitivamente un "gran compilador".
Max Langhof
2
"siempre debe hacer coincidir un nuevo con un borrado" , no si trabaja con él Qt, ya que todas sus clases base tienen recolectores de basura, por lo que solo debe usarlo newy olvidarse de él la mayor parte del tiempo. Para los elementos de la GUI, cuando el widget primario está cerrado, los secundarios quedan fuera del alcance y se recolectan basura automáticamente.
vsz
66
@vsz Incluso en Qt, cada uno newtiene una coincidencia delete; es solo que las deletes las realiza el widget principal en lugar del mismo bloque de código que las news.
jjramsey
22

Bueno, para empezar, new/ deleteno están siendo obsoleta.

Sin embargo, en su caso específico, no son la única solución. Lo que elija depende de lo que se ocultó en su comentario de "hacer algo con la matriz".

Su segundo ejemplo utiliza una extensión VLA no estándar que intenta ajustar la matriz en la pila. Esto tiene ciertas limitaciones, a saber, el tamaño limitado y la imposibilidad de usar esta memoria después de que la matriz se salga del alcance. No puede moverlo, "desaparecerá" después de que la pila se desenrolle.

Entonces, si su único objetivo es hacer un cálculo local y luego descartar los datos, en realidad podría funcionar bien. Sin embargo, un enfoque más sólido sería asignar la memoria dinámicamente, preferiblemente con std::vector. De esa manera, obtienes la capacidad de crear espacio para exactamente tantos elementos como necesites basándose en un valor de tiempo de ejecución (que es lo que vamos a hacer todo el tiempo), pero también se limpiará muy bien y podrás moverlo de este alcance si desea mantener la memoria en uso para más adelante.

Dando vueltas de nuevo al principio, vector será probablemente utilizar newunas pocas capas más profundas, pero no debe preocuparse por eso, como la interfaz que presenta es muy superior. En ese sentido, usar newy deletepuede considerarse desalentado.

Bartek Banachewicz
fuente
1
Tenga en cuenta el "... algunas capas más profundas". Si tuviera que implementar sus propios contenedores, aún debería evitar usar newy delete, sino usar punteros inteligentes como std::unique_pointer.
Max
1
que en realidad se llamastd::unique_ptr
user253751
2
@Max: std::unique_ptrllamadas destructor predeterminadas deleteo delete[], lo que significa que el objeto de propiedad debe haber sido asignado por newo de new[]todos modos, qué llamadas han estado ocultas std::make_uniquedesde C ++ 14.
Laurent LA RIZZA
15

Su segundo ejemplo utiliza matrices de longitud variable (VLA), que en realidad son una función C99no C ++!), Pero que sin embargo es compatible con g ++ .

Ver también esta respuesta .

Tenga en cuenta que las matrices de longitud variable son diferentes de new/ deletey no las "desprecian" de ninguna manera.

También tenga en cuenta que los VLA no son ISO C ++.

andreee
fuente
13

Modern C ++ proporciona formas más fáciles de trabajar con asignaciones dinámicas. Los punteros inteligentes pueden encargarse de la limpieza después de las excepciones (que pueden ocurrir en cualquier lugar si se permite) y los retornos anticipados, tan pronto como las estructuras de datos referenciadas salgan del alcance, por lo que puede tener sentido usar estas en su lugar:

  int size=100;

  // This construct requires the matching delete statement.
  auto buffer_old = new int[size];

  // These versions do not require `delete`:
  std::unique_ptr<int[]> buffer_new (new int[size]);
  std::shared_ptr<int[]> buffer_new (new int[size]); 
  std::vector<int> buffer_new (size);  int* raw_access = buffer_new.data();

Desde C ++ 14 también puedes escribir

auto buffer_new = std::make_unique<int[]>(size);

esto se ve aún mejor y evitaría la pérdida de memoria si falla la asignación. Desde C ++ 20 deberías poder hacer tanto como

auto a = std::make_shared<int[]>(size);

esto para mí todavía no se compila al momento de escribir con gcc 7.4.0. En estos dos ejemplos también usamos en autolugar de la declaración de tipo a la izquierda. En todos los casos, use la matriz como de costumbre:

buffer_old[0] = buffer_new[0] = 17;

La pérdida de memoria newy los bloqueos por duplicación deletees algo que C ++ ha sido criticado durante muchos años, siendo el "punto central" de la argumentación para cambiar a otros lenguajes. Quizás sea mejor evitarlo.

Audrius Meskauskas
fuente
Debe evitar los unique/shared_ptrconstructores a favor make_unique/shared, no solo no tiene que escribir el tipo construido dos veces (usando auto) sino que no corre el riesgo de perder memoria o recursos si la construcción falla a la mitad (si está usando un tipo que puede fallar)
Simon Buchan
2
make_unique está disponible con matrices de C ++ 14 y make_shared solo de C ++ 20. Esto rara vez es una configuración predeterminada, por lo que proponer std :: make_shared <int []> (size) me buscó con cierta anticipación.
Audrius Meskauskas
¡Lo suficientemente justo! Realmente no uso make_shared<int[]>mucho, cuando casi siempre quieres vector<int>, pero es bueno saberlo.
Simon Buchan
Exceso de pedantería, pero IIRC el unique_ptrconstructor no es arrojar, por lo Tque no tienen constructores, por lo que no hay riesgo de fugas unique_ptr(new int[size])y shared_ptrtiene lo siguiente: "Si se produce una excepción, se llama a p cuando T no es un tipo de matriz, eliminar [ ] p de otro modo ", por lo que tiene el mismo efecto -. el riesgo es para unique/shared_ptr(new MyPossiblyAllocatingType[size]).
Simon Buchan
3

new y delete no se están desaprobando.

Los objetos creados por el nuevo operador se pueden pasar por referencia. Los objetos se pueden eliminar con eliminar.

nuevo y eliminar son los aspectos fundamentales del lenguaje. La persistencia de un objeto se puede administrar usando new y delete. Definitivamente, estos no serán desaprobados.

La declaración - int array [N] es una forma de definir una matriz. La matriz se puede usar dentro del alcance del bloque de código adjunto. No se puede pasar como cómo se pasa un objeto a otra función.

Gopinath
fuente
2

El primer ejemplo necesita un delete[]al final, o tendrá una pérdida de memoria.

El segundo ejemplo usa una longitud de matriz variable que no es compatible con C ++; que sólo permite constante-expresión para longitud de la matriz .

En este caso es útil usarlo std::vector<>como solución; que envuelve todas las acciones que puede realizar en una matriz en una clase de plantilla.

maquinilla de afeitar
fuente
3
¿Qué quieres decir con "hasta C ++ 11"? Estoy bastante seguro, los VLA nunca se convirtieron en parte del estándar.
churill
mire el estándar de c ++ 14 [c ++ 14 standard] ( isocpp.org/files/papers/N3690.pdf ) en la página 184 párrafo 8.3.4
zig razor
44
Eso no es el . Estándar, pero sólo un borrador y la parte de “matrices de tiempo de ejecución con destino" no hacerlo en el estándar por lo que yo puedo decir cppreference no menciona allí. VLA
churill
1
@zigrazor cppreference.com tiene una lista de enlaces a los borradores más cercanos antes / después de la publicación de cada una de las normas. Los estándares publicados no están disponibles gratuitamente, pero estos borradores deberían estar muy cerca. Como puede ver en los números de documento, su borrador vinculado es un borrador de trabajo anterior para C ++ 14.
nogal
2
Se @learning_dude no apoyada por las normas. La respuesta es (ahora) correcta (aunque corta). Solo funciona para usted porque GCC lo permite como una extensión no estándar .
nogal
-4

La sintaxis se parece a C ++, pero el idioma es similar al antiguo Algol60. Era común tener bloques de código como este:

read n;
begin
    integer array x[1:n];
    ... 
end;

El ejemplo podría escribirse como:

while(T--) {
    int N;
    cin >> N;
    {
        int array[N];
        // Do something with 'array'
    }
}

A veces extraño esto en los idiomas actuales;)

kdo
fuente