Pregunta

Voy a empezar por decir, utilizar punteros inteligentes y usted nunca tendrá que preocuparse por esto.

¿Cuáles son los problemas con el siguiente código?

Foo * p = new Foo;
// (use p)
delete p;
p = NULL;

Esto fue provocado por una respuesta y comentarios a otra pregunta. Un comentario de Neil Butterworth genera unos upvotes:

  

Configuración de punteros a NULL después de borrar no es una buena práctica universal en C ++. Hay momentos en que es una buena cosa que hacer, y momentos en los que no tiene sentido y puede ocultar errores.

Hay un montón de circunstancias en las que no ayudaría. Pero en mi experiencia, no puede hacer daño. Alguien me ilumine.

¿Fue útil?

Solución

Configuración de un puntero a 0 (que es "nulo" en la norma C ++, el NULL definir de C es algo diferente) evita accidentes en eliminaciones dobles.

Tenga en cuenta lo siguiente:

Foo* foo = 0; // Sets the pointer to 0 (C++ NULL)
delete foo; // Won't do anything

Considerando lo siguiente:

Foo* foo = new Foo();
delete foo; // Deletes the object
delete foo; // Undefined behavior 

En otras palabras, si no se establece punteros borrados a 0, que se meterá en problemas si está haciendo dobles eliminaciones. Un argumento contra el establecimiento de punteros a 0 después de eliminación sería que hacerlo simplemente borrar máscaras doble insectos y los deja no controlada.

Lo mejor es no tener el doble de eliminar errores, obviamente, pero dependiendo de la semántica de propiedad y los ciclos de vida del objeto, esto puede ser difícil de lograr en la práctica. Yo prefiero un error de eliminación doble ciego sobre UB.

Por último, una anotación al margen con respecto a la gestión de asignación de objetos, le sugiero que eche un vistazo a std::unique_ptr de riguroso dominio / singular, std::shared_ptr de propiedad compartida, u otra aplicación puntero inteligente, dependiendo de sus necesidades.

Otros consejos

Configuración de punteros a NULL después de eliminar lo que señaló que sin duda no puede hacer daño, pero es a menudo un poco de una banda de ayuda sobre un problema más fundamental: ¿Por qué utiliza un puntero en el primer lugar? Veo dos razones típicas:

  • Usted simplemente quería algo asignada en el montón. En cuyo caso, envolviéndolo en un objeto RAII hubiera sido mucho más seguro y más limpio. Terminar el alcance del objeto RAII cuando ya no se necesita el objeto. Así es como funciona std::vector, y se resuelve el problema de forma accidental dejando punteros a cancelar la asignación de memoria alrededor. No hay punteros.
  • O tal que querías algunos complejos semántica de propiedad compartida. El puntero de regresar de new podría no ser la misma que la que se llama en delete. Varios objetos pueden haber utilizado el objeto al mismo tiempo en el ínterin. En ese caso, un puntero compartido o algo similar hubiera sido preferible.

Mi regla de oro es que si deja punteros en torno código de usuario, que está haciendo mal. El puntero no debería estar allí para que apunte a la basura en el primer lugar. Por qué no hay un objeto tomando la responsabilidad de asegurar su validez? ¿Por qué no alcance su fin cuando al cual apunta al objeto hace?

Tengo una aún mejor las mejores prácticas: Siempre que sea posible, terminar el alcance de la variable

{
    Foo* pFoo = new Foo;
    // use pFoo
    delete pFoo;
}

Siempre he establecido un puntero a NULL (ahora nullptr) después de eliminar el objeto (s) que apunta.

  1. Puede ayudar a capturar muchas referencias a la memoria liberada (asumiendo sus faltas en una plataforma DEREF de un puntero nulo).

  2. No va a coger todas las referencias a la memoria free'd si, por ejemplo, tiene copias del puntero por ahí. Pero algo es mejor que nada.

  3. Se enmascarar un doble borrar, pero me parece que esas son mucho menos comunes que los accesos a la memoria ya liberada.

  4. En muchos casos el compilador va a optimizar la basura. Por lo que el argumento de que es innecesario no me persuadir.

  5. Si usted ya está usando RAII, entonces no hay muchas deletes en su código, para empezar, por lo que el argumento de que la asignación adicional hace que el desorden no me convencen.

  6. A menudo es conveniente, cuando la depuración, para ver el valor nulo en lugar de un puntero rancio.

  7. Si esto le molesta, utilizar un puntero inteligente o una referencia en su lugar.

I también puse otros tipos de recurso maneja al valor no-recurso cuando se free'd el recurso (que es típicamente sólo en el destructor de una envoltura RAII escrito para encapsular el recurso).

trabajé en un (9 millones de declaraciones) producto comercial grande (sobre todo en C). En un momento dado, se utilizó la magia macro para anular el puntero cuando se libera la memoria. Esto expuso inmediatamente un montón de errores que acechan que se fijaron con prontitud. Por lo que puedo recordar, nunca hemos tenido un error de doble conexión.

Actualización: Microsoft cree que es una buena práctica para la seguridad y recomienda la práctica en sus políticas de SDL. Al parecer MSVC ++ 11 será pisar el puntero borrado automáticamente (en muchos casos) si se compila con la opción / SDL.

En primer lugar, hay una gran cantidad de preguntas existentes en este y temas estrechamente relacionados, por ejemplo, ¿Por qué no se elimina establece el puntero a NULL? .

En el código, el problema lo que pasa en (uso p). Por ejemplo, si en algún lugar tiene código siguiente:

Foo * p2 = p;

A continuación, el ajuste p NULL logra muy poco, ya que todavía tiene el puntero P2 que preocuparse.

Esto no quiere decir que el establecimiento de un puntero a NULL siempre es inútil. Por ejemplo, si p fuera una variable miembro apunta a un recurso que es de por vida no era exactamente la misma que la clase que contiene p, entonces el ajuste p NULL podría ser una forma útil de lo que indica la presencia o ausencia del recurso.

Si hay más código después de la delete, sí. Cuando el puntero se elimina en un constructor o al final del método o función, No.

El punto de esta parábola es para recordar al programador, durante el tiempo de ejecución, que el objeto ya se ha eliminado.

Una mejor práctica es utilizar punteros inteligentes (o compartida con ámbito), que elimina automagicamente sus objetos de destino.

Como han dicho otros, delete ptr; ptr = 0; no va a hacer que los demonios para volar fuera de su nariz. Sin embargo, sí fomenta el uso de ptr como un indicador de tipo. El código se convierte en desorden con delete y establecer el puntero a NULL. El siguiente paso consiste en dispersar if (arg == NULL) return; través de su código para proteger contra el uso accidental de un puntero NULL. El problema se produce una vez que los cheques contra NULL convierten en sus medios primarios de comprobación de estado de un objeto o programa.

Estoy seguro de que hay un código de olor sobre el uso de un puntero a modo de bandera en alguna parte, pero no he encontrado uno.

voy a cambiar ligeramente su pregunta:

  

¿Le utilizar un sin inicializar   ¿puntero? Ya sabes, uno que no lo hizo   establece en NULL o asignar la memoria se   apunta a?

Existen dos escenarios en los que ajuste el puntero a NULL se puede omitir:

  • la variable puntero sale del ámbito inmediato
  • ha sobrecargado la semántica del puntero y se utiliza su valor no sólo como un puntero de memoria, sino también como un valor clave o crudos. sin embargo este enfoque sufre de otros problemas.

Mientras tanto, el argumento de que la fijación del puntero a NULL podría ocultar errores me suena como el argumento de que no se debe corregir un error debido a que la solución podría ocultar otro error. Los únicos errores que podrían mostrar si el puntero no se establece en NULL serían los que intentan utilizar el puntero. Pero dejando a NULL en realidad causar exactamente el mismo agente que mostraría si lo usa con memoria liberada, ¿verdad?

Si usted no tiene ninguna otra restricción que obliga a cualquier juego o no establecer el puntero a NULL después de eliminarla (una tal restricción fue mencionado por Neil Butterworth), entonces mi preferencia personal es dejar que sea.

Para mí, la pregunta no es "¿es una buena idea?" pero "el comportamiento que iba a impedir o permitir tener éxito al hacer esto?" Por ejemplo, si se permite que otro código ver que el puntero ya no está disponible, por eso es otro código de intentar siquiera mirar a los punteros liberados después de ser liberados? Por lo general, es un error.

También hace más trabajo de lo necesario, así como un obstáculo para la depuración post-mortem. Cuanto menos se toca la memoria después de que no lo necesita, más fácil es averiguar por qué algo se estrelló. Muchas veces me han basado en el hecho de que la memoria está en un estado similar a cuando un error en particular se produjo para diagnosticar y solucionar dicho error.

explícitamente anulando después de eliminar fuertemente sugiere al lector que el puntero representa algo que es conceptualmente opcional . Si vi que está haciendo, me gustaría empezar a preocuparse de que por todas partes en la fuente el puntero se acostumbra que se debe probar primero contra NULL.

Si eso es lo que realmente quiere decir, que es mejor para hacer que explícita en la fuente usando algo como impulso :: opcional

optional<Foo*> p (new Foo);
// (use p.get(), but must test p for truth first!...)
delete p.get();
p = optional<Foo*>();

Pero si lo que la gente realmente quería saber el puntero ha "echado a perder", voy a lanzar en un acuerdo del 100% con los que dicen que lo mejor que puede hacer es hacer que se vaya fuera de alcance. Entonces usted está utilizando el compilador para evitar la posibilidad de malos desreferencias en tiempo de ejecución.

Ese es el bebé en toda el agua del baño C ++, no debe tirarlo. :)

En un programa bien estructurado con la comprobación de errores apropiado, no hay ninguna razón no para asignar nulo. 0 es en sí mismo un valor no válido universalmente reconocido en este contexto. Fallar dura y fallar pronto.

Muchos de los argumentos en contra de la asignación de 0 sugieren que podría ocultar un error o complicar el flujo de control. Fundamentalmente, esto es un error, ya sea aguas arriba (no es su culpa (lo siento por el mal juego de palabras)) o de otro error en nombre del programador -. Tal vez incluso una indicación de que el flujo del programa se ha vuelto demasiado complejo

Si el programador quiere introducir el uso de un puntero que puede ser nulo como un valor especial y escribir toda la evasión necesaria en torno a eso, eso es una complicación que se han introducido deliberadamente. Cuanto mejor sea la cuarentena, más pronto a encontrar casos de mal uso, y menos se puede propagar a otros programas.

Los programas bien estructurados pueden ser diseñados usando C ++ características para evitar estos casos. Puede utilizar referencias, o puede simplemente decir "paso / el uso de argumentos no válidos o nulos es un error" - un enfoque que es igualmente aplicable a los envases, tales como punteros inteligentes. El aumento de la conducta consistente y correcta prohíbe estos insectos se metan el momento.

A partir de ahí, sólo tiene un alcance muy limitado y el contexto en que pueda existir un puntero nulo (o se permite).

Lo mismo puede aplicarse a los punteros que no se const. Siguiendo el valor de un puntero es trivial, ya que su alcance es tan pequeño, y el uso inadecuado se comprueba y bien definido. Si su conjunto de herramientas y los ingenieros no puede seguir el siguiente programa una lectura rápida o hay comprobación de errores inadecuada o el flujo del programa inconsistente / indulgente, tiene otros problemas, más grande.

Por último, el compilador y el medio ambiente es probable que tenga algunos guardias para los momentos en los que le gustaría introducir errores (garabatos), detectar accesos a memoria liberada, y coger otra UB relacionada. También puede introducir los diagnósticos similares en sus programas, a menudo sin afectar a los programas existentes.

Permítanme ampliar lo que ya has puesto en su pregunta.

Esto es lo que has puesto en tu pregunta, en forma de puntos clave:


Configuración de punteros a NULL después de borrar no es una buena práctica universal en C ++. Hay momentos en que:

  • que es una buena cosa que hacer
  • y momentos en los que no tiene sentido y puede ocultar errores.

Sin embargo, no es sin tiempos cuando se trata de mal ! Va a no introducir más errores por anulando de forma explícita, no se fugas de memoria, no te provocar un comportamiento indefinido a suceder.

Por lo tanto, en caso de duda, simplemente nulo él.

Una vez dicho esto, si usted siente que tiene como null explícitamente alguna puntero, entonces a mí esto suena como usted no ha dividido un método suficiente, y debe mirar el enfoque refactorización llamado "método de extracto de" dividir el método en partes separadas.

Sí.

El único "daño" que puede hacer es introducir la ineficiencia (una operación de almacenamiento innecesario) en su programa - pero esta sobrecarga será insignificante en relación con el costo de asignar y liberar el bloque de memoria en la mayoría de los casos

Si no lo hace, tener algunos errores desagradables puntero derefernce un día.

Siempre uso una macro para borrar:

#define SAFEDELETE(ptr) { delete(ptr); ptr = NULL; }

(y similar para una matriz, free (), la liberación de asas)

También puede escribir "auto borrar" métodos que tengan una referencia a puntero del código de llamada, por lo que la fuerza puntero del código de llamada a NULL. Por ejemplo, para eliminar un subárbol de muchos objetos:

static void TreeItem::DeleteSubtree(TreeItem *&rootObject)
{
    if (rootObject == NULL)
        return;

    rootObject->UnlinkFromParent();

    for (int i = 0; i < numChildren)
       DeleteSubtree(rootObject->child[i]);

    delete rootObject;
    rootObject = NULL;
}

editar

Sí, estas técnicas violan algunas normas sobre el uso de macros (y sí, en estos días que probablemente podría conseguir el mismo resultado con plantillas) - pero utilizando durante muchos años I nunca actualizado muertos memoria - uno de los peores tiempo y más difícil y más lento para depurar los problemas que puede enfrentar. En la práctica durante muchos años se han eliminado efectivamente una clase whjole del virus por parte de todos los equipos les he introducido sucesivamente.

También hay muchas maneras en que podría poner en práctica lo anterior - Sólo estoy tratando de ilustrar la idea de obligar a la gente a NULL un puntero si se elimina un objeto, en lugar de proporcionar un medio para que liberen la memoria que no lo hace NULL Un triple de la persona que llama.

Por supuesto, el ejemplo anterior es sólo un paso hacia un auto-puntero. Lo que no me propongo porque el PO estaba pidiendo específicamente sobre el caso de no utilizar un puntero de automóviles.

"Hay momentos en que es una buena cosa que hacer, y momentos en los que no tiene sentido y puede ocultar errores"

Puedo ver dos problemas: Ese código simple:

delete myObj;
myobj = 0

se convierte a un-liner en el entorno multiproceso:

lock(myObjMutex); 
delete myObj;
myobj = 0
unlock(myObjMutex);

La "mejor práctica" de Don Neufeld no se aplican siempre. P.ej. en un proyecto de la automoción, tuvimos que establecer punteros a 0 incluso en los destructores. Me puedo imaginar en software crítico tales reglas no son infrecuentes. Es más fácil (y conveniente) a seguirlos de tratar de persuadir el equipo / código corrector para cada uso del puntero en el código, que una anulación línea de este puntero es redundante.

Otro peligro se basa en esta técnica en excepciones mediante código:

try{  
   delete myObj; //exception in destructor
   myObj=0
}
catch
{
   //myObj=0; <- possibly resource-leak
}

if (myObj)
  // use myObj <--undefined behaviour

En este código, ya sea a producir fugas de recursos y posponer el problema o se bloquea el proceso.

Por lo tanto, estos dos problemas que van espontáneamente a través de la cabeza (Herb Sutter sería seguro decir más) que para mí todas las preguntas del tipo "¿Cómo evitar el uso de inteligentes puntos y hacer el trabajo de manera segura con los punteros normales" como obsoleta .

Siempre hay colgando Punteros que preocuparse.

Si usted va a reasignar el puntero antes de utilizarlo de nuevo (derreferenciándolo, que pasa a una función, etc.), haciendo que el puntero NULL es sólo una operación adicional. Sin embargo, si usted no está seguro de si va a ser reasignado o no antes de ser usado de nuevo, poniéndolo a NULL es una buena idea.

Como muchos han dicho, es por supuesto mucho más fácil usar punteros inteligentes.

Edit: Como dijo Thomas Matthews en esta respuesta antes , si un puntero se elimina en un destructor, no hay ninguna necesidad de asignar NULL a ella, ya que no se volverá a utilizar porque el objeto está siendo destruido ya.

I puedo imaginar el establecimiento de un puntero a NULL después de borrar que sea útil en los casos raros en los que hay un legítimo escenario de la reutilización en una única función (u objeto). De lo contrario no tiene sentido - un puntero necesita señalar el tiempo que existe para algo significativo -. Periodo

Si el código no pertenece a la parte más crítica de rendimiento en su aplicación, que sea sencillo y utilizar un shared_ptr:

shared_ptr<Foo> p(new Foo);
//No more need to call delete

Se realiza el recuento de referencias y es seguro para subprocesos. Lo puede encontrar en el TR1 (espacio de nombres std :: TR1, # include ) o si su compilador no se lo proporciona, obtener de impulso.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top