2010-01-11 22 views
17

Al leer un antiguo C++ Journal que tenía, noté algo.Es Foo * f = new Foo buen código de C++

Uno de los artículos que se afirmaba que

Foo *f = new Foo();

era casi inaceptable profesional código C++ en general, y una solución de gestión automática de memoria era apropiado.

¿Esto es así?

editar: reformulado: ¿es la administración de memoria directa inaceptable para el nuevo código de C++, en general? ¿Debería auto_ptr (u otras envolturas de administración) usarse para el nuevo código más?

+16

Preguntas como esta me hacen sentir que estoy trabajando en un planeta diferente a los demás. Todas las respuestas a continuación sugieren que new/delete es obsoleto, peligroso, malvado, obsoleto; en mi (gran) empresa, ¡es básicamente el * único * mecanismo que utilizamos para asignar objetos dinámicos! Tenemos clases de contenedor, pero todas simplemente llaman a new/delete under. – Crashworks

+0

Nota: 'Foo * f = new Foo' es perfectamente válido. – sbi

+10

@Crashworks: lo que hacen debajo no importa sin embargo. Debajo, su llamada de bucle o función es solo un goto, pero eso no significa que "los gotos están bien para usar". Debajo, cada puntero y referencia se implementa pirateando direcciones de memoria, pero eso no significa que pueda tratar con seguridad punteros como direcciones de memoria. Esos son abstraídos, de forma muy similar a lo que usualmente se abstrae de las clases de contenedor y los contenedores de RAII – jalf

Respuesta

20

Este ejemplo es muy similar a Java.
En C++, solo utilizamos la gestión de memoria dinámica si es necesario.
Una mejor alternativa es simplemente declarar una variable local.

{ 
    Foo f; 

    // use f 

} // f goes out of scope and is immediately destroyed here. 

Si debe usar memoria dinámica, utilice un puntero inteligente.

// In C++14 
{ 
    std::unique_ptr<Foo> f = std::make_unique<Foo>(); // no need for new anymore 
} 

// In C++11 
{ 
    std::unique_ptr<Foo> f(new Foo); // See Description below. 
} 

// In C++03 
{ 
    std::auto_ptr<Foo> f(new Foo); // the smart pointer f owns the pointer. 
             // At some point f may give up ownership to another 
             // object. If not then f will automatically delete 
             // the pointer when it goes out of scope.. 

} 

Hay un manojo entero os punteros inteligentes proporcionados std :: int e impulso :: (ahora algunos están en std :: TR1) escoger el adecuado y utilizarlo para administrar la vida útil de su objeto.

Ver Smart Pointers: Or who owns you baby?

Técnicamente se puede utilizar la nueva/eliminar tareas de gestión de memoria.
Pero en código C++ real casi nunca se hace. Casi siempre hay una mejor alternativa para hacer la gestión de la memoria a mano.

Un ejemplo simple es std :: vector.Debajo de las cubiertas usa nuevo y eliminar. Pero nunca serías capaz de decir desde el exterior. Esto es completamente transparente para el usuario de la clase. Todo lo que el usuario sabe es que el vector tomará posesión del objeto y se destruirá cuando se destruya el vector.

0

En primer lugar, creo que debería ser Foo *f = new Foo();

Y la razón por la que no me gusta usar esa sintaxis, ya que es fácil olvidar añadir un delete al final del código y dejar su memoria una -leakin '.

+2

¿Qué otra sintaxis usarías para lograr lo mismo? –

+0

No es exactamente lo mismo, pero suelo usar 'Foo f = Foo();' – zmbush

+0

Ooops. Olvidé a los parens. A eso no me refería. :) –

6

Con algún tipo de esquema de puntero inteligente puede obtener administración de memoria automática, recuento de referencias, etc., con solo una pequeña cantidad de sobrecarga. Usted paga por eso (en memoria o en rendimiento), pero puede valer la pena pagarlo en lugar de tener que preocuparse por ello todo el tiempo.

+2

¿De qué sirve suponer que la "gestión automática de la memoria" * solo * puede hacer referencia al recuento de referencias? – jalf

+0

@jalf: en los punteros inteligentes de C++, la "gestión automática de la memoria" generalmente significa recuento de referencias. Cualquier otra cosa requeriría infraestructura. –

+2

No es cierto. 'std :: vector vec;' es gestión de memoria automática. Toda la memoria que asigna se limpia * automáticamente *. Piense en la palabra clave 'auto' (en su significado actual, pre-C++ 0x). ¿Por qué crees que se llama * auto * de todas las cosas? Porque * automáticamente * administra la vida del objeto. Todas las variables locales y los miembros de la clase se administran automáticamente * para comenzar con *. La gestión automática de memoria es la predeterminada. Solo cuando saltamos los aros y llamamos "nuevo" lo perdemos, y tenemos que crear explícitamente la infraestructura para recuperarlo. – jalf

6

Hay muy buenas razones para no utilizar sistemas automáticos de gestión de memoria en ciertos casos. Estos pueden ser el rendimiento, la complejidad de las estructuras de datos debido a referencias cíclicas, etc.

Sin embargo, recomiendo solo utilizar un generador de bits con new/malloc si tiene una buena razón para no usar algo más inteligente. Ver asignaciones desprotegidas me asusta y me hace esperar que el programador sepa lo que están haciendo.

Algún tipo de clase de puntero inteligente como boost :: shared_ptr, boost :: scoped_ptr sería un buen comienzo. (Estos serán parte del estándar C++ 0x así que no les dé miedo;))

+2

¿Está asumiendo que la "gestión automática de la memoria" solo incluye el recuento de referencias? ¿Qué tal el más simple: "Desasignar en el destructor cuando el objeto sale del alcance"? Eso es efectivamente lo que 'scoped_ptr' hace ya. ¿Cómo no es eso "gestión automática de la memoria"? – jalf

+0

"Eso es efectivamente lo que scoped_ptr hace ya" Eso es efectivamente lo que las variables normales ya hacen. – tstenner

+0

Estoy diciendo que la asignación de objetos en el montón sin algún control explícito sobre la vida útil de ese objeto es propenso a errores y generalmente es una mala idea. No estoy asumiendo nada en particular sobre el tipo de administración de memoria automática utilizada, solo digo que los punteros inteligentes son un lugar fácil para comenzar. Una lista más grande incluiría esquemas de conteo de referencias, esquemas RAII, grupos de objetos, etc. –

1

En general, no, pero el caso general no es el caso común. Es por eso que los esquemas automáticos como RAII se inventaron en primer lugar.

Desde una respuesta que escribí a otra pregunta:

El trabajo de un programador es expresar cosas con elegancia en su lengua de elección.

C++ tiene una semántica muy agradable para construcción y destrucción de objetos en la pila. Si se puede asignar un recurso durante la duración de un bloque de alcance , entonces un buen programador probablemente tomará esa ruta con una resistencia mínima de . La duración del objeto es delimitada por llaves que probablemente ya estén allí, .

Si no hay una buena manera de poner el objeto directamente en la pantalla, tal vez se puede poner dentro de otro objeto como miembro . Ahora su vida útil es un poco más de , pero C++ todavía hace mucho automáticamente. La duración del objeto está delimitada por un objeto principal: se ha delegado el problema .

Puede que no haya un padre. Lo mejor es una secuencia de padres adoptivos. Esto es lo que auto_ptr es para. Todavía es bastante bueno, porque el programador debe saber qué padre particular es el propietario. La vida útil del objeto está delimitada por la duración de su secuencia de propietarios . Un paso por la cadena en determinismo y elegancia per se es shared_ptr: vida delimitada por la unión de un grupo de propietarios.

> Pero tal vez este recurso no es concurrente con cualquier otro objeto, establezca de objetos, o el flujo de control en el sistema . Se crea después de que ocurra algún evento y se destruye en otro evento . Aunque hay muchas herramientas para delimitar vidas por delegaciones y otras vidas, no son suficientes para calcular cualquier función arbitraria . Por lo que el programador podría decidir escribir una función de varias variables para determinar si un objeto se acerca a la existencia o desaparecer, y llamar a new y delete.

Finalmente, las funciones de escritura pueden ser duras. ¡Quizás las reglas que gobiernan el objeto tomarían demasiado tiempo y memoria para calcular realmente!Y es puede ser realmente difícil expresar elegantemente, volviendo a mi punto original . Entonces para eso tenemos recolección de basura: el objeto de por vida está delimitado por cuando lo quiere y cuando no lo hace.

+0

¿Podría agregar un enlace a la otra pregunta? – jalf

+0

http://stackoverflow.com/questions/1960369/is-shared-ownership-of-objects-a-sign-of-bad-design/1960475#1960475 – Potatoswatter

2

Dejé de escribir este código hace algún tiempo. Existen varias alternativas: eliminación basada

Ámbito

{ 
    Foo foo; 
    // done with foo, release 
} 

scoped_ptr de ámbito basado en la asignación dinámica

{ 
    scoped_ptr<Foo> foo(new Foo()); 
    // done with foo, release 
} 

shared_ptr para las cosas que se deben manejar en muchos lugares

shared_ptr<Foo> foo; 
{ 
    foo.reset(new Foo()); 
} 
// still alive 
shared_ptr<Foo> bar = foo; // pointer copy 
... 
foo.reset(); // Foo still lives via bar 
bar.reset(); // released 

gestión de recursos basado en de Facory

Foo* foo = fooFactory.build(); 
... 
fooFactory.release(foo); // or it will be 
          // automatically released 
          // on factory destruction 
+10

Creo que 'Foo foo();' no compilará porque C++ pensó que es un prototipo de función. Use 'Foo foo;' en su lugar. – kennytm

+2

Tu tercer ejemplo no se compilará. – sbi

+0

@sbi, corrigió –

8

creo, el problema de todas estas "mejores prácticas ... ..." preguntas es que todos ellos consideran que el código fuera de contexto. Si pregunta "en general", debo admitir que la gestión directa de la memoria es perfectamente aceptable. Es sintácticamente legal y no viola ninguna semántica del lenguaje.

En cuanto a las alternativas (variables de pila, punteros inteligentes, etc.), todas tienen sus inconvenientes. Y ninguno de ellos tiene la flexibilidad, la administración de memoria directa tiene. El precio que debe pagar por dicha flexibilidad es su tiempo de depuración, y debe conocer todos los riesgos.

+1

+1, me tenía después de la primera oración. – sellibitze

+1

Sí, cada una de las alternativas tiene inconvenientes individuales. Pero tomados en conjunto, ¿no tienen tanta flexibilidad como la administración de memoria "directa"? ¿Hay algo que pueda hacer con llamadas directas nuevas/borrar, que no se pueden lograr con * ninguna * de las alternativas? – jalf

+0

a jalf: algunas veces quiere que el destructor del objeto sea llamado * exactamente * en el lugar específico. No puede lograrlo con ningún tipo de punteros compartidos, ya que posponen la destrucción de objetos hasta que nadie apunte al objeto. Los indicadores débiles proporcionan gastos generales, que pueden ser inaceptables. Los indicadores de alcance no pueden hacer nada si desea extender la duración del objeto fuera del alcance. – SadSido

4

Depende de exactamente lo que queremos decir.

  • ¿Nunca se debe usar new para asignar memoria? Por supuesto que debería, no tenemos otra opción. new es la forma de asignar objetos dinámicamente en C++. Cuando necesitamos asignar dinámicamente un objeto de tipo T, hacemos new T(...).
  • ¿Debería llamarse newde forma predeterminada cuando queremos instanciar un objeto nuevo? NO. En java o C#, new se utiliza para crear objetos nuevos, por lo que se usa en todas partes. en C++, solo se usa para asignaciones de pila. Casi todos los objetos deben ser asignados a la pila (o creados en el lugar como miembros de la clase) para que las reglas de alcance del lenguaje nos ayuden a administrar sus tiempos de vida. new no es a menudo necesario. Por lo general, cuando queremos asignar nuevos objetos en el montón, lo hace como parte de una colección más grande, en cuyo caso debe simplemente insertar el objeto en su contenedor STL y dejar que se preocupe por asignar y desasignar la memoria. Si solo necesita un único objeto, normalmente se puede crear como un miembro de clase o una variable local, sin usar new.
  • ¿Debe estar presente new en su código de lógica de negocios? Rara vez, si alguna vez. Como se mencionó anteriormente, puede y debe ser ocultado dentro de las clases de envoltura. std::vector, por ejemplo, asigna dinámicamente la memoria que necesita. Por lo tanto, el usuario del vector no tiene por qué preocuparse. Solo creo un vector en la pila, y se encarga de las asignaciones de montón para mí.Cuando un vector u otra clase de contenedor no es adecuado, podemos escribir nuestro propio contenedor RAII, que asigna algo de memoria en el constructor con new, y lo libera en el destructor. Y esa envoltura se puede asignar a la pila, por lo que el usuario de la clase nunca tiene que llamar al new.

Uno de los artículos que se afirmaba que Foo *f = new Foo(); era casi inaceptable profesional código C++ en general, y una solución de gestión automática de memoria era apropiado.

Si quieren decir lo que creo que significan, entonces tienen razón. Como dije anteriormente, new generalmente debe ocultarse en clases de contenedor, donde la administración automática de la memoria (en forma de vida del alcance y los objetos que tienen llamados a sus destructores cuando salen del alcance) puede encargarse de ello. El artículo no dice "nunca asigne nada en el montón" o nunca use new ", sino simplemente" Cuando use new, no solo almacene un puntero en la memoria asignada. Colóquelo dentro de algún tipo de clase que pueda ocuparse de liberarlo cuando se salga del alcance.

En lugar de Foo *f = new Foo();, debe utilizar uno de estos:

Scoped_Foo f; // just create a wrapper which *internally* allocates what it needs on the heap and frees it when it goes out of scope 
shared_ptr<Foo> f = new Foo(); // if you *do* need to dynamically allocate an object, place the resulting pointer inside a smart pointer of some sort. Depending on circumstances, scoped_ptr, or auto_ptr may be preferable. Or in C++0x, unique_ptr 
std::vector<Foo> v; v.push_back(Foo()); // place the object in a vector or another container, and let that worry about memory allocations. 
6

Si está utilizando excepciones que tipo de código está prácticamente garantizado para dar lugar a fugas Recource. Incluso si deshabilita las excepciones, es muy fácil deshacerse de la limpieza cuando se sincroniza manualmente con delete.

+0

+10 (pero SO solo permitirá +1, lo siento) – PaulMcG

0

En general, su ejemplo no es una excepción segura y, por lo tanto, no debe utilizarse. Si la línea sigue directamente a los nuevos lanzamientos? La pila se desenrolla y acaba de filtrar la memoria. Un puntero inteligente se encargará de resolverlo como parte de la pila. Si tiende a no manejar excepciones, entonces no hay inconveniente fuera de los problemas de RAII.

Cuestiones relacionadas