web-dev-qa-db-fra.com

Vérifier si cela est nul

Est-il jamais judicieux de vérifier si ce est nul?

Disons que j'ai une classe avec une méthode; à l'intérieur de cette méthode, je vérifie this == NULL, et si c'est le cas, retournez un code d'erreur.

Si ce est nul, cela signifie que l'objet est supprimé. La méthode est-elle même capable de renvoyer quelque chose?

pdate: J'ai oublié de mentionner que la méthode peut être appelée à partir de plusieurs threads et cela peut entraîner la suppression de l'objet pendant qu'un autre thread se trouve à l'intérieur de la méthode.

59
user156144

Est-il jamais judicieux de vérifier ce == null? J'ai trouvé cela en faisant une revue de code.

En C++ standard, ce n'est pas le cas, car tout appel sur un pointeur nul est déjà un comportement indéfini, donc tout code reposant sur de telles vérifications n'est pas standard (il n'y a aucune garantie que la vérification sera même exécutée).

Notez que cela vaut également pour les fonctions non virtuelles.

Cependant, certaines implémentations autorisent this==0, Et par conséquent les bibliothèques écrites spécifiquement pour ces implémentations l'utilisent parfois comme hack. Un bon exemple d'une telle paire est VC++ et MFC - Je ne me souviens pas du code exact, mais je me souviens distinctement avoir vu quelque part if (this == NULL) contrôles dans le code source MFC.

Il peut également être là comme aide au débogage, car à un moment donné dans le passé, ce code a été frappé avec this==0 En raison d'une erreur dans l'appelant, donc un chèque a été inséré pour en détecter les futures instances. Une affirmation aurait cependant plus de sens pour de telles choses.

Si ceci == null, cela signifie que l'objet est supprimé.

Non, ça ne veut pas dire ça. Cela signifie qu'une méthode a été appelée sur un pointeur nul ou sur une référence obtenue à partir d'un pointeur nul (bien que l'obtention d'une telle référence soit déjà U.B.). Cela n'a rien à voir avec delete et ne nécessite aucun objet de ce type à avoir existé.

74
Pavel Minaev

Votre note sur les discussions est inquiétante. Je suis presque sûr que vous avez une condition de course qui peut conduire à un crash. Si un thread supprime un objet et met le pointeur à zéro, un autre thread peut passer un appel via ce pointeur entre ces deux opérations, conduisant à this étant non nul et non valide, entraînant un plantage. De même, si un thread appelle une méthode alors qu'un autre thread est en train de créer l'objet, vous pouvez également obtenir un plantage.

Réponse courte, vous devez vraiment utiliser un mutex ou quelque chose pour synchroniser l'accès à cette variable. Vous devez vous assurer que this est jamais nul ou vous allez avoir des problèmes.

26
Tim Sylvester

Je sais que c'est vieux mais j'ai l'impression que maintenant nous avons affaire à C++ 11-17 quelqu'un devrait mentionner les lambdas. Si vous capturez ceci dans un lambda qui va être appelé de manière asynchrone à un moment ultérieur, il est possible que votre objet "this" soit détruit avant que ce lambda soit appelé.

c'est-à-dire le passer en tant que rappel à une fonction coûteuse en temps qui est exécutée à partir d'un thread séparé ou simplement en mode asynchrone en général

EDIT: Juste pour être clair, la question était "Est-il jamais judicieux de vérifier si cela est nul"? Je propose simplement un scénario où cela a du sens qui pourrait devenir plus répandu avec l'utilisation plus large du C++ moderne.

Exemple artificiel: ce code est entièrement exécutable. Pour voir un comportement dangereux, il suffit de commenter l'appel à un comportement sûr et de ne pas commenter l'appel de comportement dangereux.

#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;
}
6
Josh Sanders

FWIW, j'ai utilisé des contrôles de débogage pour (this != NULL) dans les assertions qui ont aidé à détecter le code défectueux. Non pas que le code serait nécessairement allé trop loin sans un crash, mais sur les petits systèmes embarqués qui n'ont pas de protection de mémoire, les assertions ont en fait aidé.

Sur les systèmes avec protection de la mémoire, le système d'exploitation frappera généralement une violation d'accès s'il est appelé avec un pointeur NULL this, il y a donc moins de valeur à affirmer this != NULL. Cependant, voir le commentaire de Pavel pour savoir pourquoi ce n'est pas nécessairement sans valeur, même sur des systèmes protégés.

6
Michael Burr

Je sais que c'est une vieille question, mais j'ai pensé partager mon expérience avec l'utilisation de la capture 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;
}

Défauts de ce segment de code

$ g++ -std=c++14  uniqueptr.cpp  
$ ./a.out 
Segmentation fault (core dumped)

Si je supprime l'instruction std::cout De lambda_func Le code s'exécute jusqu'à son terme.

Il semble que cette instruction f->lambda_func([f = std::move(f)] () mutable { traite les captures lambda avant l'appel de la fonction membre.

0
Prasad Joshi

Votre méthode sera très probablement (peut varier selon les compilateurs) être en mesure de s'exécuter et également de pouvoir renvoyer une valeur. Tant qu'il n'accède à aucune variable d'instance. S'il essaie cela, il plantera.

Comme d'autres l'ont souligné, vous ne pouvez pas utiliser ce test pour voir si un objet a été supprimé. Même si vous le pouviez, cela ne fonctionnerait pas, car l'objet peut être supprimé par un autre thread juste après le test mais avant d'exécuter la ligne suivante après le test. Utilisez plutôt la synchronisation des threads.

Si this est nul, il y a un bogue dans votre programme, très probablement dans la conception de votre programme.

0
Carsten