Nullptr y verificar si un puntero apunta a un objeto válido

8 minutos de lectura

avatar de usuario
catarsis fatal

En un par de mis proyectos de código más antiguos cuando nunca había oído hablar de los punteros inteligentes, cada vez que necesitaba verificar si el puntero aún apuntaba a un objeto válido, siempre hacía algo como esto…

object * meh = new object;
if(meh) 
    meh->member;

O cuando necesitaba eliminar el objeto de forma segura, algo como esto

if(meh)
{
    delete meh;
    meh = 0;
}

Bueno, ahora he aprendido acerca de los problemas que pueden surgir al usar objetos y punteros en expresiones booleanas tanto con números literales, de la manera difícil:. Y ahora también he aprendido de la característica no tan nueva pero genial de C++, el nullptr palabra clave. Pero ahora tengo curiosidad.

Ya revisé y revisé la mayor parte de mi código para que, por ejemplo, al eliminar objetos ahora escribo

if(meh)
{
    delete meh;
    meh = nullptr;
}

Ahora me pregunto sobre el booleano. Cuando pase, simplemente diga un int en una declaración if como esta,

int meh;
if(meh)

Luego, comprueba implícitamente el cero sin necesidad de escribirlo.

if(meh == 0) // does the exact same check

Ahora, ¿C++ hará lo mismo con los punteros? Si pasa un char * como este a una declaración if?

char * meh;
if(meh)

Entonces, ¿lo comparará implícitamente con nullptr? Debido a cuánto tiempo he estado escribiendo estos ifs como este, es una segunda naturaleza en este punto verificar si los punteros son válidos antes de usarlos escribiendo if (objeto *) y luego llamando a sus miembros. Si esta no es la funcionalidad, ¿por qué no? ¿Demasiado difícil de implementar? Resolvería algunos problemas al eliminar otra pequeña forma en que podría estropear su código.

  • Tú haces no necesita ckeck punteros antes deleteEn g. Es completamente seguro para delete a nullptr.

    – norte 1.8e9-dónde-está-mi-participación m.

    1 de julio de 2012 a las 5:13

  • En tu último ejemplo, ¿quisiste decir char * meh = nullptr; if(meh)? El puntero no está inicializado.

    – Jesse bueno

    1 de julio de 2012 a las 5:18

  • el resultado de tu new expresión nunca será nula, en su lugar se utilizan excepciones. Como se mencionó, eliminar nulo está bien, no hace nada. Además, generalmente es mejor no restablecer el valor de un puntero a nulo. La última vez que se usa debe ser la última vez que no es nulo, por lo que tener acceso a un puntero eliminado debe considerarse un error; establecerlo en nulo oculta eso.

    – GManNickG

    1 de julio de 2012 a las 5:22

  • “Siempre estableceré un puntero en cero después de invalidarlo, por lo que sé que un puntero que no sea cero es válido” es un anti-patrón. ¿Qué sucede si tienes dos punteros al mismo objeto? Poner uno a cero no afectará al otro.

    –David Schwartz

    1 de julio de 2012 a las 5:27


  • @FatalCatharsis: Así será. Pero se producirán los mismos errores de tiempo de ejecución si alguna otra pieza de código elimina alguna otra copia del puntero y luego le quita la referencia. Por lo tanto, poner a cero el puntero no es necesario ni suficiente. Si lo usa donde no es necesario, simplemente está siendo ineficiente. Pero si lo usas donde no es suficiente… boom. (Eso no significa que nunca debas hacerlo, por supuesto. Pero rara vez es la herramienta adecuada para el trabajo y se usa en muchos lugares donde está muy, muy mal. Eso es lo que lo convierte en un anti-patrón).

    –David Schwartz

    1 de julio de 2012 a las 5:34


avatar de usuario
guau

En C, cualquier cosa que no sea 0 es verdadera. Entonces, ciertamente puedes usar:

if (ptrToObject) 
    ptrToObject->doSomething();

para desreferenciar punteros de forma segura.

C++11 cambia un poco el juego, nullptr_t es un tipo del cual nullptr es una instancia; la representación de nullptr_t es específico de la implementación. Entonces un compilador puede definir nullptr_t como quiera. Solo necesita asegurarse de que puede imponer la restricción adecuada en el lanzamiento de un nullptr_t a diferentes tipos, de los cuales booleano está permitido, y asegúrese de que pueda distinguir entre un nullptr_t y 0

Asi que nullptr será adecuada e implícitamente echada a la booleano false siempre que el compilador siga la especificación del lenguaje C++11. Y el fragmento anterior todavía funciona.

Si elimina un objeto al que se hace referencia, nada cambia.

delete ptrToObject;
assert(ptrToObject);
ptrToObject = nullptr;
assert(!ptrToObject);    

Debido a cuánto tiempo he estado escribiendo estos ifs como este, es una segunda naturaleza en este punto verificar si los punteros son válidos antes de usarlos escribiendo if (objeto *) y luego llamando a sus miembros.

No. Mantenga un gráfico adecuado de objetos (preferiblemente utilizando punteros únicos/inteligentes). Como se señaló, no hay forma de determinar si un puntero que no es nullptr apunta a un objeto válido o no. Usted tiene la responsabilidad de mantener el ciclo de vida de todos modos… es por eso que los contenedores de puntero existen en primer lugar.

De hecho, debido a que el ciclo de vida de compartido y débil los punteros están bien definidos, tienen azúcar sintáctica que le permite usarlos de la manera que desee usar punteros desnudos, donde los punteros válidos tienen un valor y todos los demás son nullptr:

Compartido

#include <iostream>
#include <memory>

void report(std::shared_ptr<int> ptr) 
{
    if (ptr) {
        std::cout << "*ptr=" << *ptr << "\n";
    } else {
        std::cout << "ptr is not a valid pointer.\n";
    }
}

int main()
{
    std::shared_ptr<int> ptr;
    report(ptr);

    ptr = std::make_shared<int>(7);
    report(ptr);
}

Débil

#include <iostream>
#include <memory>

void observe(std::weak_ptr<int> weak) 
{
    if (auto observe = weak.lock()) {
        std::cout << "\tobserve() able to lock weak_ptr<>, value=" << *observe << "\n";
    } else {
        std::cout << "\tobserve() unable to lock weak_ptr<>\n";
    }
}

int main()
{
    std::weak_ptr<int> weak;
    std::cout << "weak_ptr<> not yet initialized\n";
    observe(weak);

    {
        auto shared = std::make_shared<int>(42);
        weak = shared;
        std::cout << "weak_ptr<> initialized with shared_ptr.\n";
        observe(weak);
    }

    std::cout << "shared_ptr<> has been destructed due to scope exit.\n";
    observe(weak);
}

Ahora, ¿C++ hará lo mismo con los punteros? Si pasa un char * como este a una declaración if?

Así que para responder a la pregunta: con desnudo punteros, no. Con envuelto punteros, .

Envuelva sus punteros, amigos.

  • eso causaría un error de tiempo de ejecución, si olvidaste poner a cero el puntero. pero si el puntero se compara implícitamente con nullptr, entonces, ya sea que el puntero sea 0 o se dirija a algo, se resolverá como falso cuando el objeto no esté allí.

    – Catarsis fatal

    1 de julio de 2012 a las 5:23

  • Sí. Entonces, ¿cuál es tu pregunta?

    – guau

    1 de julio de 2012 a las 5:28

  • @FatalCatharsis Tanto en C como en C++, un puntero nulo se compara como igual a cero integral y un puntero no nulo se compara como no igual a cero integral, sin importar cuál sea la representación bit a bit del puntero.

    – efímero

    1 de julio de 2012 a las 5:52

  • -_- Simplemente explicó mi respuesta… de lo contrario, habría publicado la suya.

    – guau

    1 de julio de 2012 a las 5:56


  • ¿Qué quiere decir con “el comportamiento es específico de la implementación”? Se comporta como el estándar dice que debe comportarse. Su representación e implementación depende de la implementación (pero eso no es nada nuevo. La representación de un puntero nulo de “estilo antiguo” también estaba definida por la implementación)

    – jalf

    1 de julio de 2012 a las 6:35

No es posible probar si un puntero apunta a un objeto válido o no. Si el puntero no es nulo pero no apunta a un objeto válido, el uso del puntero provoca un comportamiento indefinido. Para evitar este tipo de error, usted tiene la responsabilidad de tener cuidado con la vida útil de los objetos a los que se apunta; y las clases de puntero inteligente ayudan con esta tarea.

Si meh es un puntero en bruto, entonces no hay diferencia alguna entre if (meh) y if (meh != 0) y if (meh != nullptr). Todos proceden si el puntero no es nulo.

Hay una conversión implícita del literal. 0 a nullptr .

  • Bueno, hay maneras de hacerlo. He visto código que incrusta un self puntero y eso comprueba self == thisy ceros self sobre la destrucción. Posible pérdida de tiempo y espacio.

    – usuario207421

    20 de agosto de 2019 a las 1:06

  • @ user207421 tal vez podría publicar una respuesta con sus pensamientos (y un código específico), me parece que está describiendo algo diferente

    –MM

    20 de agosto de 2019 a las 3:01

¿Ha sido útil esta solución?