Проверка, если это ноль - PullRequest
       10

Проверка, если это ноль

59 голосов
/ 04 декабря 2009

Имеет ли смысл когда-либо проверять, является ли этот нулевым?

Скажем, у меня есть класс с методом; внутри этого метода я проверяю this == NULL и, если это так, возвращаю код ошибки.

Если , это равно нулю, то это означает, что объект удален. Может ли метод хоть что-нибудь вернуть?

Обновление: Я забыл упомянуть, что метод может быть вызван из нескольких потоков, и это может привести к удалению объекта, когда другой метод находится внутри метода.

Ответы [ 8 ]

74 голосов
/ 04 декабря 2009

Есть ли смысл проверять это == ноль? Я нашел это во время обзора кода.

В стандартном C ++ этого не происходит, поскольку любой вызов нулевого указателя уже является неопределенным поведением, поэтому любой код, основанный на таких проверках, является нестандартным (нет гарантии, что проверка будет выполнена).

Обратите внимание, что это верно и для не виртуальных функций.

Однако некоторые реализации разрешают this==0, и, следовательно, библиотеки, написанные специально для этих реализаций, иногда используют его как хакерский. Хороший пример такой пары - VC ++ и MFC - я не помню точный код, но я отчетливо помню, как где-то в исходном коде MFC if (this == NULL) проверял.

Он также может быть там как средство отладки, потому что в какой-то момент в прошлом этот код получал this==0 из-за ошибки в вызывающей программе, поэтому была вставлена ​​проверка, чтобы перехватить будущие экземпляры этого. Утверждение будет иметь больше смысла для таких вещей, хотя.

Если это == ноль, то это означает, что объект удален.

Нет, это не значит. Это означает, что метод был вызван по нулевому указателю или по ссылке, полученной из нулевого указателя (хотя получение такой ссылки уже U.B.). Это не имеет ничего общего с delete и не требует, чтобы какие-либо объекты этого типа когда-либо существовали.

26 голосов
/ 04 декабря 2009

Ваше замечание о темах вызывает беспокойство. Я уверен, что у вас есть состояние гонки, которое может привести к аварии. Если поток удаляет объект и обнуляет указатель, другой поток может выполнить вызов через этот указатель между этими двумя операциями, что приведет к тому, что this будет ненулевым и недопустимым, что приведет к сбою. Точно так же, если поток вызывает метод, в то время как другой поток находится в процессе создания объекта, вы также можете получить сбой.

Короткий ответ, вам действительно нужно использовать мьютекс или что-то еще, чтобы синхронизировать доступ к этой переменной. Вам нужно убедиться, что this равно никогда null, иначе у вас будут проблемы.

6 голосов
/ 25 февраля 2016

Я знаю, что это старо, но я чувствую, что теперь, когда мы имеем дело с C ++ 11-17, кто-то должен упомянуть лямбду. Если вы запишете это в лямбду, которая будет вызываться асинхронно в более поздний момент времени , возможно, ваш «этот» объект будет уничтожен до того, как эта лямбда будет вызвана.

т.е. передача его в качестве обратного вызова к некоторой дорогостоящей во времени функции, которая запускается из отдельного потока или вообще асинхронно

РЕДАКТИРОВАТЬ: Просто чтобы быть ясным, вопрос был: "Есть ли когда-нибудь смысл проверить, является ли это пустым"? Я просто предлагаю сценарий, в котором есть смысл, который может стать более распространенным при более широком использовании современного C ++.

Придуманный пример: Этот код полностью работает. Чтобы увидеть небезопасное поведение, просто закомментируйте призыв к безопасному поведению и раскомментируйте вызов небезопасного поведения.

#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 голосов
/ 04 декабря 2009

FWIW, я использовал проверки отладки для (this != NULL) в утверждениях, которые помогли отловить дефектный код. Не то чтобы код обязательно зашел бы слишком далеко в случае сбоя, но в небольших встроенных системах, не имеющих защиты памяти, утверждения действительно помогли.

В системах с защитой памяти ОС обычно сталкивается с нарушением доступа, если вызывается с указателем NULL this, поэтому при утверждении this != NULL меньше значения. Однако, см. Комментарий Павла, почему он не обязательно бесполезен даже на защищенных системах.

0 голосов
/ 04 июня 2017

Я знаю, что это старый вопрос, однако я подумал, что поделюсь своим опытом использования лямбда-захвата

#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;
}

Ошибка этого сегмента кода

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

Если я удаляю оператор std::cout из lambda_func Код выполняется до конца.

Кажется, что этот оператор f->lambda_func([f = std::move(f)] () mutable { обрабатывает лямбда-захват до вызова функции-члена.

0 голосов
/ 24 февраля 2013

Это просто указатель, переданный в качестве первого аргумента функции (что и делает метод). Пока вы не говорите о виртуальных методах и / или виртуальном наследовании, тогда да, вы можете обнаружить, что выполняете метод экземпляра с нулевым экземпляром. Как уже говорили другие, вы почти наверняка не продвинетесь слишком далеко с этим выполнением до того, как возникнут проблемы, но надежное кодирование должно, вероятно, проверять эту ситуацию с утверждением. По крайней мере, это имеет смысл, когда вы подозреваете, что это может произойти по какой-то причине, но нужно точно определить, в каком стеке классов / вызовов он происходит.

0 голосов
/ 04 декабря 2009

Скорее всего, ваш метод (может отличаться в разных компиляторах) сможет работать, а также сможет возвращать значение. Пока это не обращается ни к каким переменным экземпляра. Если он попытается это сделать, он потерпит крах.

Как отмечали другие, вы не можете использовать этот тест, чтобы увидеть, был ли объект удален. Даже если бы вы могли, это не сработало бы, потому что объект может быть удален другим потоком сразу после теста, но до того, как вы выполните следующую строку после теста. Вместо этого используйте синхронизацию потоков.

Если this равно нулю, в вашей программе есть ошибка, скорее всего, в дизайне вашей программы.

0 голосов
/ 04 декабря 2009

Я бы также добавил, что обычно лучше избегать значений NULL или NULL. Я думаю, что стандарт здесь снова меняется, но сейчас 0 - это действительно то, что вы хотите проверить, чтобы быть абсолютно уверенным, что вы получаете то, что хотите.

...