提问人:user156144 提问时间:12/4/2009 最后编辑:Jan Schultkeuser156144 更新时间:9/19/2023 访问量:45700
检查“this”是否为空有意义吗?
Does it ever make sense to check if "this" is null?
问:
假设我有一个带有成员函数的类;在该方法中,我检查 ,如果是,则返回错误代码。this == nullptr
如果为 null,则表示对象已删除。该方法甚至能够返回任何内容吗?this
更新:我忘了提到该方法可以从多个线程调用,并且可能会导致对象被删除,而另一个线程位于成员函数内部。
答:
检查这个==null有意义吗?我在进行代码审查时发现了这一点。
在标准 C++ 中,它不会,因为对 null 指针的任何调用都已经是未定义的行为,因此任何依赖于此类检查的代码都是非标准的(甚至不能保证检查会被执行)。
请注意,这也适用于非虚拟功能。
但是,某些实现允许,因此专门为这些实现编写的库有时会将其用作黑客。VC++ 和 MFC 是这种对的一个很好的例子 - 我不记得确切的代码,但我清楚地记得在某处的 MFC 源代码中看到过检查。this==0
if (this == NULL)
它也可能作为调试辅助工具存在,因为在过去某个时候,由于调用方的错误,此代码被击中,因此插入了一个检查以捕获将来的实例。不过,对于这样的事情,断言会更有意义。this==0
如果 this == null,则表示对象已删除。
不,不是那个意思。这意味着在空指针或从空指针获取的引用上调用了方法(尽管获取这样的引用已经是 U.B.)。这与 无关,也不要求任何这种类型的对象曾经存在过。delete
评论
delete
delete
this != NULL
delete
不过,可能并不总是得到 L 值,请考虑一下delete this + 0;
你关于线程的注释令人担忧。我很确定你有一个可能导致崩溃的竞争条件。如果一个线程删除了一个对象并将指针归零,则另一个线程可能会在这两个操作之间通过该指针进行调用,从而导致非 null 且无效,从而导致崩溃。同样,如果一个线程调用一个方法,而另一个线程正在创建对象,您也可能会崩溃。this
简短的回答,您确实需要使用互斥锁或其他东西来同步对此变量的访问。您需要确保它永远不会为 null,否则您将遇到问题。this
评论
operator->
FWIW,我之前在断言中使用过调试检查,这有助于捕获有缺陷的代码。并不是说代码在不崩溃的情况下一定会走得太远,但是在没有内存保护的小型嵌入式系统上,这些断言实际上有所帮助。(this != NULL)
在具有内存保护的系统上,如果使用 NULL 指针调用,操作系统通常会遇到访问冲突,因此断言 的值较小。但是,请参阅 Pavel 的评论,了解为什么即使在受保护的系统上也不一定毫无价值。this
this != NULL
评论
this
this
this
我还要补充一点,通常最好避免 null 或 NULL。我认为这里的标准再次发生变化,但现在 0 确实是您想要检查的,以绝对确定您得到了您想要的东西。
评论
您的方法很可能(可能因编译器而异)能够运行并且能够返回值。只要它不访问任何实例变量。如果它尝试这样做,它将崩溃。
正如其他人指出的那样,您不能使用此测试来查看对象是否已被删除。即使可以,它也不起作用,因为该对象可能会在测试之后但在测试后执行下一行之前被另一个线程删除。请改用线程同步。
如果为 null,则程序中存在 bug,很可能是程序设计中的错误。this
这只是一个指针,作为函数的第一个参数传递(这正是使它成为方法的原因)。只要你不是在谈论虚拟方法和/或虚拟继承,那么是的,你会发现自己正在执行一个实例方法,而实例为空。正如其他人所说,在问题出现之前,你几乎肯定不会在执行中走得太远,但健壮的编码可能应该通过断言来检查这种情况。至少,当您怀疑它可能由于某种原因而发生,但需要准确跟踪它发生在哪个类/调用堆栈中时,这是有道理的。
我知道这已经很老了,但我觉得现在我们正在处理 C++11-17,有人应该提到 lambdas。如果您将其捕获到稍后将异步调用的 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;
}
评论
this
我知道这是一个老问题,但我想我会分享我使用 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)
如果我从代码中删除语句,则代码将运行完成。std::cout
lambda_func
似乎,此语句在调用成员函数之前处理 lambda 捕获。f->lambda_func([f = std::move(f)] () mutable {
是的,在至少一个编译器上,检查是有意义的。它将按您的预期工作,并且可以触发它。它是否有用是值得怀疑的,因为格式良好的代码不应该在 null 指针上调用成员函数,但很便宜。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 this pointer!
Clang 16.0.0 会警告您不能为 null,将检查变成空操作,并打印两次。GCC 13.2 还会警告您正在 null 指针上调用成员函数,并且还会打印两次。this
hello, world!
hello, world!
在现实世界的实际使用中,一个永远不需要取消引用的成员函数将被声明,因此使用 Clang 或 GCC 编译的触发此错误的实际代码(例如传递包含对象指针的默认初始化)将在现代操作系统上段错误。但是,健全性检查对优化它的编译器毫无用处。this
static
struct
评论
((Foo*)0)->foo()
foo()
this