Проверка, является ли это нулевым

Имеет ли смысл проверять, является ли это null?

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

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

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

Ответ 1

Имеет ли смысл проверять это == null? Я нашел это, выполняя проверку кода.

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

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

EDIT: Чтобы быть ясным, вопрос был: "Имеет ли смысл проверять, является ли это нулевым". Я просто предлагаю сценарий, где это имеет смысл, который может стать более распространенным с более широким использованием современного С++.

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

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

Ответ 5

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

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

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

Ответ 6

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

#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 { обрабатывает лямбда-захваты перед вызовом функции-члена.

Ответ 7

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

Ответ 8

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

Ответ 9

this == NULL может оказаться полезным для восстановления ситуации (например, необязательный механизм делегирования распределителя, который будет возвращаться к malloc/free). Я не уверен в его стандарте, но если вы НИКОГДА не называете какую-либо виртуальную функцию на нем и, конечно, не имеете доступа к члену внутри метода в этой ветке, нет реальной причины для сбоя. В противном случае у вас очень плохой компилятор, который использует указатель виртуальных функций, когда им это не нужно, а затем, прежде чем строго соблюдать стандарт, лучше изменить свой компилятор.

Иногда это полезно, потому что чтение и рефакторинг могут в очень немногих случаях превышать стандарт (когда его явно не undefined поведение для всех существующих компиляторов).

Об этой статье: "Все еще сравнивая "this" указатель на нуль", возможно, причина в том, что автор статьи имеет меньше понимания того, что делает компилятор, чем команда разработчиков Microsoft, которая написала MFC.