检查“this”是否有意义?为空?

发布于 2024-08-13 14:44:56 字数 198 浏览 3 评论 0原文

假设我有一个带有成员函数的类;在该方法中,我检查 this == nullptr,如果是,则返回错误代码。

如果 this 为 null,则意味着该对象被删除。该方法是否能够返回任何内容?

更新:我忘了提到该方法可以从多个线程调用,并且当另一个线程位于成员函数内部时,它可能会导致对象被删除。

Say I have a class with a member function; inside that method, I check this == nullptr, and if it is, return an error code.

If this is null, then that means the object is deleted. Is the method even able to return anything?

Update: I forgot to mention that the method can be called from multiple threads and it may cause the object to be deleted while another thread is inside the member function.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

国粹 2024-08-20 14:44:56

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

在标准 C++ 中,情况并非如此,因为对空指针的任何调用都已经是未定义的行为,因此依赖此类检查的任何代码都是非标准的(甚至无法保证检查会被执行)。

请注意,这也适用于非虚拟函数。

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

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

如果 this == null 则意味着该对象被删除。

不,不是这个意思。这意味着在空指针上或在从空指针获得的引用上调用了方法(尽管获得这样的引用已经是UB)。这与删除无关,并且不需要任何该类型的对象曾经存在。

Does it ever make sense to check for this==null? I found this while doing a code review.

In standard C++, it does not, because any call on a null pointer is already undefined behavior, so any code relying on such checks is non-standard (there's no guarantee that the check will even be executed).

Note that this holds true for non-virtual functions as well.

Some implementations permit this==0, however, and consequently libraries written specifically for those implementations will sometimes use it as a hack. A good example of such a pair is VC++ and MFC - I don't recall the exact code, but I distinctly remember seeing if (this == NULL) checks in MFC source code somewhere.

It may also be there as a debugging aid, because at some point in the past this code was hit with this==0 because of a mistake in the caller, so a check was inserted to catch future instances of that. An assert would make more sense for such things, though.

If this == null then that means the object is deleted.

No, it doesn't mean that. It means that a method was called on a null pointer, or on a reference obtained from a null pointer (though obtaining such a reference is already U.B.). This has nothing to do with delete, and does not require any objects of this type to have ever existed.

止于盛夏 2024-08-20 14:44:56

您关于线程的注释令人担忧。我很确定你有可能导致崩溃的竞争条件。如果一个线程删除了一个对象并将指针归零,另一个线程可能会在这两个操作之间通过该指针进行调用,从而导致 this 为非空且无效,从而导致崩溃。同样,如果一个线程调用一个方法,而另一个线程正在创建对象,您也可能会崩溃。

简而言之,您确实需要使用互斥锁或其他东西来同步对此变量的访问。您需要确保this决不为空,否则将会遇到问题。

Your note about threads is worrisome. I'm pretty sure you have a race condition that can lead to a crash. If a thread deletes an object and zeros the pointer, another thread could make a call through that pointer between those two operations, leading to this being non-null and also not valid, resulting in a crash. Similarly, if a thread calls a method while another thread is in the middle of creating the object, you may also get a crash.

Short answer, you really need to use a mutex or something to synchonize access to this variable. You need to ensure that this is never null or you're going to have problems.

王权女流氓 2024-08-20 14:44:56

我知道这已经很旧了,但我觉得现在我们正在处理 C++11-17,有人应该提到 lambda。 如果您将其捕获到将在稍后某个时间点异步调用的 lambda 中,则您的“this”对象可能会在调用该 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;
}

I know that this is old but I feel like now that we're dealing with C++11-17 somebody should mention lambdas. If you capture this into a lambda that is going to be called asynchronously at a later point in time, it is possible that your "this" object gets destroyed before that lambda is invoked.

i.e passing it as a callback to some time-expensive function that is run from a separate thread or just asynchronously in general

EDIT: Just to be clear, the question was "Does it ever make sense to check if this is null" I am merely offering a scenario where it does make sense that might become more prevalent with the wider use of modern C++.

Contrived example:
This code is completely runable. To see unsafe behavior just comment out the call to safe behavior and uncomment the unsafe behavior call.

#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;
}
奢望 2024-08-20 14:44:56

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

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

FWIW, I have used debugging checks for (this != NULL) in assertions before which have helped catch defective code. Not that the code would have necessarily gotten too far with out a crash, but on small embedded systems that don't have memory protection, the assertions actually helped.

On systems with memory protection, the OS will generally hit an access violation if called with a NULL this pointer, so there's less value in asserting this != NULL. However, see Pavel's comment for why it's not necessarily worthless on even protected systems.

就像说晚安 2024-08-20 14:44:56

是的,在至少一个编译器上,检查是有意义的。它将按照您的预期工作,并且可以触发它。它是否有用是值得怀疑的,因为格式良好的代码永远不应该在空指针上调用成员函数,但 assert(this); 很便宜。

以下代码将在 MSVC 19.37 上编译并按预期运行。即使使用 /std:c++20 /Wall /external:anglebrackets /external:W0,它也不会发出警告。

#include <cstdlib>
#include <functional>
#include <iostream>

using std::cerr, std::cout;

class Foo {
public:
    void sillyContrivance() const noexcept {
        if (this) {
            cout << "hello, world!\n";
        } else {
            cerr << "Null this pointer!\n";
        }
     }
};

int main() {
    static_cast<Foo*>(nullptr)->sillyContrivance();
    const auto closure = std::bind(&Foo::sillyContrivance, static_cast<Foo*>(nullptr));
    closure();
}

程序打印 Null thispointer! 两次。

Clang 16.0.0 会警告您 this 不能为 null,将检查转为无操作,并打印 hello, world! 两次。 GCC 13.2 还会警告您正在对空指针调用成员函数,并且还会打印 hello, world! 两次。

在现实世界中,实际使用中,永远不需要取消引用 this 的成员函数将被声明为 static,因此使用 Clang 或 GCC 编译的实际代码会触发此错误(例如传递包含对象指针的默认初始化的struct)在现代操作系统上会出现段错误。然而,健全性检查对于优化它的编译器来说是没有用的。

On at least one compiler, yes, the check makes sense. It will work as you expect and it is possible to trigger it. Whether it’s useful is questionable, since well-formed code should never call a member function on a null pointer, but assert(this); is cheap.

The following code will compile on MSVC 19.37 and run as expected. It will not issue a warning, even with /std:c++20 /Wall /external:anglebrackets /external:W0.

#include <cstdlib>
#include <functional>
#include <iostream>

using std::cerr, std::cout;

class Foo {
public:
    void sillyContrivance() const noexcept {
        if (this) {
            cout << "hello, world!\n";
        } else {
            cerr << "Null this pointer!\n";
        }
     }
};

int main() {
    static_cast<Foo*>(nullptr)->sillyContrivance();
    const auto closure = std::bind(&Foo::sillyContrivance, static_cast<Foo*>(nullptr));
    closure();
}

The program prints Null this pointer! twice.

Clang 16.0.0 will warn you that this cannot be null, turn the check into a no-op, and print hello, world! twice. GCC 13.2 will additionally warn you that you are calling a member function on a null pointer, and also print hello, world! twice.

In real-world, practical use, a member function that never needs to dereference this would have been declared static, so realistic code compiled with Clang or GCC that triggers this bug (such as passing around a default-initialized struct containing an object pointer) would segfault on modern OSes. However, the sanity check would be useless on compilers that optimize it away.

记忆之渊 2024-08-20 14:44:56

您的方法很可能(可能因编译器而异)能够运行并且也能够返回一个值。只要它不访问任何实例变量即可。如果尝试这样做就会崩溃。

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

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

Your method will most likely (may vary between compilers) be able to run and also be able to return a value. As long as it does not access any instance variables. If it tries this it will crash.

As others pointed out you can not use this test to see if an object has been deleted. Even if you could, it would not work, because the object may be deleted by another thread just after the test but before you execute the next line after the test. Use Thread synchronization instead.

If this is null there is a bug in your program, most likely in the design of your program.

风渺 2024-08-20 14:44:56

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

I'd also add that it's usually better to avoid null or NULL. I think the standard is changing yet again here but for now 0 is really what you want to check for to be absolutely sure you're getting what you want.

断肠人 2024-08-20 14:44:56

这只是作为第一个参数传递给函数的指针(这正是它成为方法的原因)。只要您不谈论虚拟方法和/或虚拟继承,那么您就可以发现自己正在使用空实例执行实例方法。正如其他人所说,在问题出现之前,您几乎肯定不会在执行过程中走得太远,但健壮的编码可能应该使用断言来检查这种情况。至少,当您怀疑它可能由于某种原因发生,但需要准确追踪它发生在哪个类/调用堆栈时,这是有意义的。

This is just a pointer passed as the first argument to a function (which is exactly what makes it a method). So long as you're not talking about virtual methods and/or virtual inheritance, then yes, you can find yourself executing an instance method, with a null instance. As others said, you almost certainly won't get very far with that execution before problems arise, but robust coding should probably check for that situation, with an assert. At least, it makes sense when you suspect it could be occuring for some reason, but need to track down exactly which class / call stack it's occurring in.

与之呼应 2024-08-20 14:44:56

我知道这是一个老问题,但我想我会分享使用 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;
}

此代码段错误的

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

经验如果我从 lambda_func 中删除 std::cout 语句代码运行完成。

看起来,这个语句 f->lambda_func([f = std::move(f)] () mutable { 在调用成员函数之前处理 lambda 捕获。

I know this is a old question, however I thought I will share my experience with use of 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;
}

This code segment faults

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

If I remove the std::cout statement from lambda_func The code runs to completion.

It seems like, this statement f->lambda_func([f = std::move(f)] () mutable { processes lambda captures before member function is invoked.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文