c++ - secundaria - Comprobando si esto es nulo
acertijos mentales de logica (9)
¿Tiene alguna vez verificar esto == nulo? Encontré esto mientras hacía una revisión del código.
En C ++ estándar, no lo hace, porque cualquier llamada en un puntero nulo ya es un comportamiento indefinido, por lo que cualquier código que dependa de dichas verificaciones no es estándar (no hay garantía de que el cheque se vaya a ejecutar).
Tenga en cuenta que esto también es cierto para las funciones no virtuales.
Sin embargo, algunas implementaciones permiten this==0
y, en consecuencia, las bibliotecas escritas específicamente para esas implementaciones algunas veces lo usarán como un hack. Un buen ejemplo de dicho par es VC ++ y MFC; no recuerdo el código exacto, pero recuerdo claramente haber visto if (this == NULL)
verifica el código fuente de MFC en alguna parte.
También puede estar allí como una ayuda de depuración, porque en algún momento en el pasado este código se golpeó con this==0
debido a un error en la persona que llama, por lo que se insertó un cheque para capturar las instancias futuras de eso. Sin embargo, una afirmación tendría más sentido para tales cosas.
Si esto == null significa que el objeto está eliminado.
No, eso no significa eso. Significa que se llamó a un método en un puntero nulo, o en una referencia obtenida de un puntero nulo (aunque obtener dicha referencia ya es UB). Esto no tiene nada que ver con la delete
, y no requiere ningún objeto de este tipo que haya existido alguna vez.
¿Tiene sentido alguna vez verificar si esto es nulo?
Digamos que tengo una clase con un método; dentro de ese método, compruebo que this == NULL
, y si lo es, devuelvo un código de error.
Si esto es nulo, significa que el objeto se ha eliminado. ¿El método puede devolver algo?
Actualización: Olvidé mencionar que el método puede invocarse desde varios hilos y puede hacer que el objeto se elimine mientras otro hilo está dentro del método.
Esto es solo un puntero pasado como el primer argumento de una función (que es exactamente lo que lo convierte en un método). Mientras no esté hablando de métodos virtuales y / o herencia virtual, entonces sí, puede encontrarse ejecutando un método de instancia, con una instancia nula. Como otros dijeron, es casi seguro que no llegarás muy lejos con esa ejecución antes de que surjan problemas, pero la codificación robusta probablemente debería verificar esa situación, con una afirmación. Al menos, tiene sentido cuando sospechas que podría estar ocurriendo por alguna razón, pero necesitas rastrear exactamente en qué clase / pila de llamadas está ocurriendo.
FWIW, he utilizado comprobaciones de depuración para (this != NULL)
en aserciones anteriores que han ayudado a detectar código defectuoso. No es que el código necesariamente haya llegado demasiado lejos sin un bloqueo, pero en sistemas integrados pequeños que no tienen protección de memoria, las afirmaciones en realidad ayudaron.
En los sistemas con protección de memoria, el SO generalmente golpeará una violación de acceso si se llama con un NULL this
puntero, por lo que hay menos valor para afirmar this != NULL
. Sin embargo, consulte el comentario de Pavel sobre por qué no es necesariamente inútil incluso en sistemas protegidos.
Lo más probable es que su método (puede variar entre compiladores) pueda ejecutarse y también pueda devolver un valor. Siempre que no acceda a ninguna variable de instancia. Si lo intenta, se bloqueará.
Como otros señalaron, no puede usar esta prueba para ver si un objeto ha sido eliminado. Incluso si pudiera, no funcionaría, porque el objeto puede ser eliminado por otro hilo justo después de la prueba, pero antes de ejecutar la siguiente línea después de la prueba. Use la sincronización de subprocesos en su lugar.
Si this
es nulo, hay un error en su programa, muy probablemente en el diseño de su programa.
Sé que esta es una vieja pregunta, sin embargo, pensé que iba a compartir mi experiencia con el uso de la captura Lambda
#include <iostream>
#include <memory>
using std::unique_ptr;
using std::make_unique;
using std::cout;
using std::endl;
class foo {
public:
foo(int no) : no_(no) {
}
template <typename Lambda>
void lambda_func(Lambda&& l) {
cout << "No is " << no_ << endl;
l();
}
private:
int no_;
};
int main() {
auto f = std::make_unique<foo>(10);
f->lambda_func([f = std::move(f)] () mutable {
cout << "lambda ==> " << endl;
cout << "lambda <== " << endl;
});
return 0;
}
Este código falla en el segmento
$ g++ -std=c++14 uniqueptr.cpp
$ ./a.out
Segmentation fault (core dumped)
Si lambda_func
instrucción std::cout
de lambda_func
el código se ejecuta hasta completarse.
Parece que esta afirmación f->lambda_func([f = std::move(f)] () mutable {
procesa las capturas lambda antes de invocar la función miembro.
Sé que esto es viejo pero siento que ahora que estamos lidiando con C ++ 11-17 alguien debería mencionar a lambdas. Si captura esto en una lambda que se llamará de manera asincrónica en un momento posterior , es posible que su objeto "this" se destruya antes de invocar esa lambda.
es decir, pasándolo como una devolución de llamada a alguna función costosa en el tiempo que se ejecuta desde un hilo separado o simplemente asíncrona en general
EDITAR: Para ser claros, la pregunta era: "¿Tiene sentido alguna vez comprobar si esto es nulo?" Simplemente estoy ofreciendo un escenario donde tiene sentido que podría ser más frecuente con el uso más amplio de C ++ moderno.
Ejemplo: este código es completamente ejecutable. Para ver el comportamiento inseguro, simplemente comente la llamada al comportamiento seguro y elimine el comentario de la llamada de comportamiento inseguro.
#include <memory>
#include <functional>
#include <iostream>
#include <future>
class SomeAPI
{
public:
SomeAPI() = default;
void DoWork(std::function<void(int)> cb)
{
DoAsync(cb);
}
private:
void DoAsync(std::function<void(int)> cb)
{
std::cout << "SomeAPI about to do async work/n";
m_future = std::async(std::launch::async, [](auto cb)
{
std::cout << "Async thread sleeping 10 seconds (Doing work)./n";
std::this_thread::sleep_for(std::chrono::seconds{ 10 });
// Do a bunch of work and set a status indicating success or failure.
// Assume 0 is success.
int status = 0;
std::cout << "Executing callback./n";
cb(status);
std::cout << "Callback Executed./n";
}, cb);
};
std::future<void> m_future;
};
class SomeOtherClass
{
public:
void SetSuccess(int success) { m_success = success; }
private:
bool m_success = false;
};
class SomeClass : public std::enable_shared_from_this<SomeClass>
{
public:
SomeClass(SomeAPI* api)
: m_api(api)
{
}
void DoWorkUnsafe()
{
std::cout << "DoWorkUnsafe about to pass callback to async executer./n";
// Call DoWork on the API.
// DoWork takes some time.
// When DoWork is finished, it calls the callback that we sent in.
m_api->DoWork([this](int status)
{
// Undefined behavior
m_value = 17;
// Crash
m_data->SetSuccess(true);
ReportSuccess();
});
}
void DoWorkSafe()
{
// Create a weak point from a shared pointer to this.
std::weak_ptr<SomeClass> this_ = shared_from_this();
std::cout << "DoWorkSafe about to pass callback to async executer./n";
// Capture the weak pointer.
m_api->DoWork([this_](int status)
{
// Test the weak pointer.
if (auto sp = this_.lock())
{
std::cout << "Async work finished./n";
// If its good, then we are still alive and safe to execute on this.
sp->m_value = 17;
sp->m_data->SetSuccess(true);
sp->ReportSuccess();
}
});
}
private:
void ReportSuccess()
{
// Tell everyone who cares that a thing has succeeded.
};
SomeAPI* m_api;
std::shared_ptr<SomeOtherClass> m_data = std::shared_ptr<SomeOtherClass>();
int m_value;
};
int main()
{
std::shared_ptr<SomeAPI> api = std::make_shared<SomeAPI>();
std::shared_ptr<SomeClass> someClass = std::make_shared<SomeClass>(api.get());
someClass->DoWorkSafe();
// Comment out the above line and uncomment the below line
// to see the unsafe behavior.
//someClass->DoWorkUnsafe();
std::cout << "Deleting someClass/n";
someClass.reset();
std::cout << "Main thread sleeping for 20 seconds./n";
std::this_thread::sleep_for(std::chrono::seconds{ 20 });
return 0;
}
Su nota sobre hilos es preocupante. Estoy bastante seguro de que tienes una condición de carrera que puede provocar un accidente. Si un subproceso elimina un objeto y lo pone a cero, otro subproceso podría hacer una llamada a través de ese puntero entre esas dos operaciones, lo que hace que no sea nulo y tampoco válido, lo que da como resultado un bloqueo. Del mismo modo, si un hilo llama a un método mientras otro hilo está en el medio de crear el objeto, también puede obtener un bloqueo.
Respuesta corta, realmente necesita usar un mutex o algo para sincronizar el acceso a esta variable. Debe asegurarse de que this
nunca sea nulo o de que tenga problemas.
También agregaría que generalmente es mejor evitar nulos o NULL. Creo que el estándar está cambiando una vez más aquí, pero por ahora 0 es realmente lo que desea comprobar para estar absolutamente seguro de que está obteniendo lo que desea.
esto == NULL podría ser útil para tener un comportamiento de repliegue (por ejemplo, un mecanismo de delegación de asignador opcional que retrocedería a malloc / free). No estoy seguro de su estándar, pero si NUNCA llama a ninguna función virtual y, por supuesto, no tiene acceso de miembro dentro del método en esa rama, no hay motivo real para bloquear. De lo contrario, tiene un compilador muy pobre que utiliza un puntero de función virtual cuando no es necesario y, antes de cumplir estrictamente con el estándar, es mejor que cambie su compilador.
En algún momento es útil porque la readministración y la refactorización podrían, en unos pocos casos, ganar el estándar (cuando obviamente no es un comportamiento indefinido para todos los compiladores existentes).
Sobre el artículo: "Todavía comparando" este "Puntero con nulo", tal vez la razón es que el autor del artículo tiene menos comprensión de lo que hace un compilador que el equipo de software de Microsoft que escribió el MFC.