c++ - fuente - declaracion de variables programacion
¿Es Foo*f=el nuevo código de C++ de Foo bueno? (10)
Leyendo un viejo C ++ Journal que tenía, noté algo.
Uno de los artículos afirmaba que
Foo *f = new Foo();
El código C ++ profesional era casi inaceptable en general, y una solución de administración de memoria automática era apropiada.
¿Esto es así?
edición: reformulado: ¿es la administración de memoria directa inaceptable para el nuevo código C ++, en general ? ¿Se debería usar auto_ptr (o los otros envoltorios de administración) para la mayoría del código nuevo?
Con algún tipo de esquema de puntero inteligente puede obtener administración de memoria automática, conteo de referencias, etc., con solo una pequeña cantidad de sobrecarga. Usted paga por eso (en memoria o rendimiento), pero puede valer la pena pagarlo en lugar de tener que preocuparse por ello todo el tiempo.
Creo que el problema de todas estas preguntas "... mejores prácticas ..." es que todas consideran el código sin contexto. Si pregunta "en general", debo admitir que la administración de memoria directa es perfectamente aceptable. Es sintácticamente legal y no viola ninguna semántica de lenguaje.
En cuanto a las alternativas (variables de pila, punteros inteligentes, etc.), todas tienen sus inconvenientes. Y ninguno de ellos tiene la flexibilidad, la gestió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.
Dejé de escribir ese código hace algún tiempo. Hay varias alternativas:
Eliminación basada en el alcance
{
Foo foo;
// done with foo, release
}
scoped_ptr para la asignación dinámica basada en el alcance
{
scoped_ptr<Foo> foo( new Foo() );
// done with foo, release
}
shared_ptr para cosas que deben manejarse 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 basada en facory
Foo* foo = fooFactory.build();
...
fooFactory.release( foo ); // or it will be
// automatically released
// on factory destruction
Depende exactamente de lo que queremos decir.
- ¿Nunca se debe utilizar el nuevo 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 unanew T(...)
. - ¿Se debería llamar a
new
forma predeterminada cuando queremos instanciar un nuevo objeto? No En java o C #,new
se usa para crear nuevos objetos, así que lo usas en todas partes. en C ++, solo se utiliza para asignaciones de pila. Casi todos los objetos deben asignarse en pila (o crearse en el lugar como miembros de la clase) para que las reglas de alcance del idioma nos ayuden a administrar sus vidas.new
no es necesario a menudo. 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 simplemente debe empujar el objeto en su contenedor de STL y dejar que se preocupe por asignar y desasignar memoria. Si solo necesita un único objeto, normalmente se puede crear como un miembro de la clase o una variable local, sin usar unnew
. - ¿Debería estar presente el
new
código de lógica de negocio? Rara vez, si alguna vez Como se mencionó anteriormente, puede y debe estar oculto dentro de las clases de envoltura.std::vector
por ejemplo, asigna dinámicamente la memoria que necesita. Así que el usuario delvector
no tiene que preocuparse. Acabo de crear un vector en la pila y se encarga de las asignaciones del montón para mí. Cuando un vector u otra clase contenedora no es adecuada, podemos escribir nuestro propio contenedor RAII, que asigna algo de memoria en el constructor con lanew
, y la libera en el destructor. Y esa envoltura puede entonces ser asignada a la pila, para que el usuario de la clase nunca tenga que llamarnew
.
Uno de los artículos afirmó que
Foo *f = new Foo();
El código C ++ profesional era casi inaceptable en general, y una solución de administración de memoria automática era apropiada.
Si significan lo que creo que significan, entonces tienen razón. Como dije anteriormente, las clases envueltas, por lo general, deberían estar ocultas, donde la gestión automática de la memoria (en la forma de vida útil del ámbito y los objetos que tienen sus destructores llamados cuando se salen del ámbito) pueden encargarse de ello. El artículo no dice "nunca asignes nada en el montón" ni nunca uses new
", sino simplemente" Cuando uses new
, no solo guardes un puntero en la memoria asignada. Colóquelo dentro de algún tipo de clase que pueda ocuparse de liberarlo cuando esté fuera del alcance.
En lugar de Foo *f = new Foo();
, deberías usar 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.
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.
De una respuesta escribí a otra pregunta:
El trabajo de un programador es expresar las cosas con elegancia en el idioma de su elección.
C ++ tiene una semántica muy buena para la construcción y destrucción de objetos en la pila. Si se puede asignar un recurso por la duración de un bloque de alcance, entonces un buen programador probablemente tomará ese camino de menor resistencia. La vida útil del objeto está delimitada por llaves que probablemente ya estén allí.
Si no hay una buena manera de colocar el objeto directamente en la pila, tal vez se pueda colocar dentro de otro objeto como miembro. Ahora su vida útil es un poco más larga, pero C ++ todavía se hace mucho automáticamente. La vida útil del objeto está delimitada por un objeto principal: el problema se ha delegado.
Puede que no haya un padre, sin embargo. Lo siguiente mejor es una secuencia de padres adoptivos. Esto es para lo que es
auto_ptr
Aún es bastante bueno, porque el programador debe saber qué padre particular es el propietario. La vida útil del objeto está delimitada por la vida útil de su secuencia de propietarios. Un paso por la cadena en determinismo y per se elegancia esshared_ptr
: vida delimitada por la unión de un grupo de propietarios.> Pero quizás este recurso no sea concurrente con ningún otro objeto, conjunto de objetos o flujo de control en el sistema. Se crea sobre un evento que sucede y se destruye sobre otro evento. Aunque hay muchas herramientas para delimitar las vidas de las delegaciones y otras, no son suficientes para calcular ninguna función arbitraria. Por lo tanto, el programador puede decidir escribir una función de varias variables para determinar si un objeto está apareciendo o desapareciendo, y llamar a
new
ydelete
.Finalmente, escribir funciones puede ser difícil. ¡Tal vez las reglas que gobiernan el objeto tomen demasiado tiempo y memoria para calcular realmente! Y podría ser muy difícil expresarlos con elegancia, volviendo a mi punto original. Entonces para eso tenemos recolección de basura: el tiempo de vida del objeto está delimitado por cuando lo quieres y cuando no.
En general, su ejemplo no es una excepción segura y, por lo tanto, no debe utilizarse. ¿Si la línea sigue directamente los nuevos lanzamientos? La pila se desenrolla y acabas de perder memoria. Un puntero inteligente se encargará de usted como parte del despliegue de la pila. Si tiende a no manejar las excepciones, entonces no hay recuperación fuera de los problemas de RAII.
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 porque es fácil olvidarse de agregar un delete
al final del código y dejar la memoria a un lado ".
Este ejemplo es muy similar a Java.
En C ++ solo usamos 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, use 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 una gran cantidad de punteros inteligentes que se proporcionan int std :: y boost :: (ahora algunos están en std :: tr1) elija el apropiado y utilícelo para administrar la vida útil de su objeto.
Ver los punteros inteligentes: ¿O quién es el dueño de tu bebé?
Técnicamente puedes usar new / delete para hacer la gestión de memoria.
Pero en el código real de C ++ casi nunca se hace. Casi siempre hay una mejor alternativa para hacer la administración de la memoria a mano.
Un ejemplo simple es el std :: vector. Bajo las portadas usa nuevo y borrado. Pero nunca serías capaz de decirlo desde afuera. 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.
No.
Hay muy buenas razones para no usar sistemas de administración de memoria automática en ciertos casos. Estos pueden ser el rendimiento, la complejidad de las estructuras de datos debido a referencias cíclicas, etc.
Sin embargo, solo recomiendo usar un poiner sin procesar con new / malloc si tiene una buena razón para no usar algo más inteligente. Ver asignaciones no protegidas 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 te asustes;))
Si está utilizando excepciones, está prácticamente garantizado que ese tipo de código dará lugar a nuevas pérdidas de recursos. Incluso si deshabilita las excepciones, la limpieza es muy fácil de preparar cuando se empareja manualmente nuevo con eliminar.