假设我有一个类,其中有一个成员函数。在该方法内部,我检查this == nullptr
,如果是,则返回错误代码。
如果this
为空,则表示对象已被删除。该方法是否能够返回任何内容?
更新:我忘了提及该方法可以从多个线程调用,并且可能会导致对象在另一个线程处于成员函数内时被删除。
delete
关键字时,通常相反的情况会发生 -- 如果你使用 delete
删除一个对象,它并不会被设置为 NULL。如果你试图调用已删除对象的方法,你会发现 this != NULL
,但是如果该对象的内存已经被其他对象重新使用,那么它很可能会崩溃或表现异常。因此需要格外小心。 - Adam Rosenfielddelete
通常会得到一个左值,但是需要考虑 delete this + 0;
这种情况,它可能不一定会得到一个左值。请注意,这只是翻译,不包括任何解释或其他内容。 - GManNickG你关于线程的笔记有点令人担忧。我很确定你存在竞态条件,可能导致崩溃。如果一个线程删除对象并将指针置为零,在这两个操作之间,另一个线程可以通过该指针调用方法,导致this
非空但无效,从而导致崩溃。同样地,如果一个线程在另一个线程正在创建对象时调用了一个方法,你也可能会遇到崩溃。
简单来说,你真的需要使用互斥锁或其他东西来同步访问这个变量。你需要确保this
永远不为空,否则你会遇到问题。
operator->
的左操作数永远不为null :) 但除此之外,我希望我能够给这个+10分。 - Pavel Minaev我知道这已经过时了,但是我觉得现在我们正在处理C++11-17,有人应该提到lambda表达式。如果你将"this"对象捕捉到一个lambda表达式中,并且该lambda表达式将在稍后的时间点以异步方式调用,那么在该lambda表达式被调用之前,"this"对象可能会被销毁。
例如将其作为回调函数传递给一些耗费时间的函数,该函数从单独的线程或通常情况下以异步方式运行
编辑:只是为了明确,问题是“是否有意义检查这是否为空”,我仅提供一个场景,在这种场景下,它确实有意义,并且随着现代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
指针吗? - interfect顺便说一下,我以前在断言中使用过(this != NULL)
的调试检查,有助于捕获有缺陷的代码。虽然代码不一定会崩溃,但在没有内存保护的小型嵌入式系统上,这些断言实际上是有帮助的。
在具有内存保护的系统上,如果使用空的this
指针调用操作系统,通常会出现访问冲突,因此断言this != NULL
的价值较小。但是,请参考Pavel的评论,了解即使在受保护的系统上也不一定是毫无意义的。
this
作为参数传递。 - Pavel Minaevthis
的价值方面措辞稍微缓和了一些。 - Michael Burrthis
指针。我希望这句话有些意义。 - Michael Burrassert(this);
的开销很小。/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();
}
你的方法很可能(在编译器之间可能会有所不同)能够运行并返回一个值。只要它不访问任何实例变量。如果它尝试这样做,它将崩溃。
正如其他人指出的那样,您不能使用此测试来查看对象是否已删除。即使您可以,它也不起作用,因为对象可能会在测试后但在执行测试后的下一行之前被另一个线程删除。改用线程同步。
如果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)
lambda_func
中删除std::cout
语句,代码将运行完成。f->lambda_func([f = std::move(f)] () mutable {
在调用成员函数之前处理lambda捕获。这只是传递给函数的第一个参数指针(这正是使它成为方法的原因)。只要您不涉及虚方法和/或虚继承,那么是的,您可以发现自己执行一个实例方法,但实例为空。正如其他人所说,在出现问题之前,您几乎肯定无法在执行中取得什么成果,但健壮的编码应该检查该情况,并断言。至少,当您怀疑出现某种情况时,需要跟踪确切发生在哪个类/调用堆栈中时,这是有意义的。
((Foo*)0)->foo()
是完全有效的语法。只要foo()
不是虚函数,这甚至可以在大多数编译器上运行,但这样做很糟糕。 - Tim Sylvesterthis
变成 null。 - Steve Jessop