检查这是否为空

时间:2009-12-04 00:08:50

标签: c++ pointers null

检查是否为空是否有意义?

说我有一个方法课;在该方法中,我检查this == NULL,如果是,则返回错误代码。

如果为空,则表示该对象已删除。该方法甚至能够返回任何东西吗?

更新:我忘了提到可以从多个线程调用该方法,并且可能导致该对象被删除而另一个线程在该方法内。

9 个答案:

答案 0 :(得分:74)

  

检查这个== null是否有意义?我在进行代码审查时发现了这一点。

在标准C ++中,它没有,因为对空指针的任何调用都是未定义的行为,因此任何依赖此类检查的代码都是非标准的(不能保证甚至会执行检查)。

请注意,这也适用于非虚拟功能。

但是,某些实现允许this==0,因此专门为这些实现编写的库有时会将其用作hack。这样一对的一个很好的例子是VC ++和MFC - 我不记得确切的代码,但我清楚地记得在某处看到MFC源代码中的if (this == NULL)检查。

它也可能作为调试辅助工具,因为在过去的某个时刻,由于调用者的错误,此代码被this==0命中,因此插入检查以捕获其未来的实例。但是,对于这样的事情,断言会更有意义。

  

如果这= = null则表示该对象已被删除。

不,这并不意味着。这意味着在空指针或从空指针获得的引用上调用方法(尽管获得这样的引用已经是U.B.)。这与delete无关,也不需要任何此类对象存在。

答案 1 :(得分:26)

你对线程的说明令人担忧。我很确定你的比赛条件会导致车祸。如果某个线程删除了一个对象并将指针归零,则另一个线程可以通过该指针在这两个操作之间进行调用,从而导致this非空且无效,从而导致崩溃。类似地,如果一个线程在另一个线程正在创建对象的过程中调用一个方法,那么你也可能会崩溃。

简短回答,您确实需要使用互斥锁或其他东西来同步访问此变量。您需要确保this 从不 null或者您将遇到问题。

答案 2 :(得分:6)

FWIW,我在断言中使用了(this != NULL)的调试检查,之前有助于捕获有缺陷的代码。并不是说代码在崩溃时必然会得到太多,但在没有内存保护的小型嵌入式系统上,断言实际上有所帮助。

在具有内存保护的系统上,如果使用NULL this指针调用,操作系统通常会遇到访问冲突,因此断言this != NULL的价值较低。但是,请参阅Pavel的评论,为什么即使受保护的系统也不一定无价值。

答案 3 :(得分:6)

我知道这已经过时了,但我觉得现在我们正在处理C ++ 11-17,有人应该提一下lambdas。 如果你把它捕获到一个将在以后异步调用的lambda ,那么你的"这个"在调用lambda之前,对象被销毁。

即将其作为回调传递给某个时间非常昂贵的函数,该函数从一个单独的线程运行或者一般只是异步运行

编辑:为了清楚起见,问题是"检查这是否为空是否有意义"我只是提供一个有意义的场景,它可能会随着现代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;
}

答案 4 :(得分:0)

您的方法很可能(可能在编译器之间有所不同)能够运行并且还能够返回值。只要它不访问任何实例变量。如果它试图这会崩溃。

正如其他人指出的那样,您无法使用此测试来查看对象是否已被删除。即使你可以,它也行不通,因为对象可能在测试之后但在测试之后执行下一行之前被另一个线程删除。改为使用线程同步。

如果this为空,则您的程序中存在错误,很可能是您的程序设计中的错误。

答案 5 :(得分:0)

我知道这是一个老问题,但我想我会分享使用Lambda捕获的经验

delayed job

此代码段出错

#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在调用成员函数之前处理lambda捕获。

答案 6 :(得分:-1)

我还要补充一点,通常最好避免使用null或NULL。我认为标准在这里再次发生变化,但现在0确实是你想要检查的,以确保你得到你想要的东西。

答案 7 :(得分:-1)

这只是作为函数的第一个参数传递的指针(这正是使它成为方法的原因)。只要您不是在谈论虚方法和/或虚拟继承,那么您可以发现自己正在使用null实例执行实例方法。正如其他人所说的那样,在出现问题之前,你几乎肯定不会对执行情况做出很大的贡献,但强大的编码应该可以通过断言来检查这种情况。至少,当你怀疑它可能由于某种原因而出现时,它是有道理的,但需要确切地追踪它正在发生的类/调用堆栈。

答案 8 :(得分:-1)

this == NULL可能有助于产生回退行为(例如,可回退到malloc / free的可选分配器委托机制)。 我不确定它的标准,但如果你从来没有调用任何虚函数,当然在该分支的方法中没有成员访问,没有实际的原因崩溃。 否则你有一个非常差的编译器,当它们不需要时使用虚函数指针,然后在严格遵守标准之前,你最好更改你的编译器。

有时它很有用,因为可读性和重构可能在极少数情况下胜过标准(当它显然不是所有现有编译器的未定义行为时)。

关于文章:“仍在比较”这个“空指针”可能是因为文章的作者对编写器的作用比对编写MFC的Microsoft软件团队的理解要少。