问题
I have an std::function object I'm using as a callback to some event. I'm assigning a lambda to this object, within which, I assign the object to a different lambda mid execution. I get a segfault when I do this. Is this not something I'm allowed to do? If so, why? And how would I go about achieving this?
declaration:
std::function<void(Data *)> doCallback;
calling:
//
// This gets called after a sendDataRequest call returns with data
//
void onIncomingData(Data *data)
{
if ( doCallback )
{
doCallback(data);
}
}
assignment:
doCallback =
[=](Data *data)
{
//
// Change the callback within itself because we want to do
// something else after getting one request
//
doCallback =
[=](Data *data2)
{
... do some work ...
};
sendDataRequest();
};
sendDataRequest();
回答1:
The standard does not specify when in the operation of std::function::operator()
that the function uses its internal state object. In practice, some implementations use it after the call.
So what you did was undefined behaviour, and in particular it crashes.
struct bob {
std::function<void()> task;
std::function<void()> next_task;
void operator()(){
next_task=task;
task();
task=std::move(next_task);
}
}
now if you want to change what happens when you next invoke bob
within bob()
, simply set next_task
.
回答2:
Short answer
It depends on whether, after the (re)assignment, the lambda being called accesses any of its non static data members or not. If it does then you get undefined behavior. Otherwise, I believe nothing bad should happen.
Long answer
In the OP's example, a lambda object -- denoted here by l_1
-- held by a std::function
object is invoked and, during its execution, the std::function
object is assigned to another lambda -- denoted here by l_2
.
The assignment calls template<class F> function& operator=(F&& f);
which, by 20.8.11.2.1/18, has the effects of
function(std::forward<F>(f)).swap(*this);
where f
binds to l_2
and *this
is the std::function
object being assigned to. At this time, the temporary std::function
holds l_2
and *this
holds l_1
. After the swap
the temporary holds l_1
and *this
holds l_2
(*). Then the temporary is destroyed and so is l_1
.
In summary, while running operator()
on l_1
this object gets destroyed. Then according to 12.7/1
For an object with a non-trivial constructor, referring to any non-static member or base class of the object before the constructor begins execution results in undefined behavior. For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor finishes execution results in undefined behavior.
Lambdas non static data members correspond its captures. So if you don't access them, then it should be fine.
There's one more important point raised by Yakk's answer. As far as I understand, the concern was whether std::function::operator()
, after having forwarded the call to l_1
, tries to access l_1
(which is now dead) or not? I don't think this is the case because the effects of std::function::operator()
don't imply that. Indeed, 20.8.11.2.4 says that the effect of this call is
INVOKE(f, std::forward<ArgTypes>(args)..., R)
(20.8.2), wheref
is the target object (20.8.1) of*this
.
which basicallky says that std::function::operator()
calls l_1.operator()
and does nothing else (at least, nothing that is detectable).
(*) I'm putting details on how the interchange happens under the carpet but the idea remains valid. (E.g. what if the temporary holds a copy of l_1
and not a pointer to it?)
来源:https://stackoverflow.com/questions/23357236/re-assigning-an-stdfunction-object-while-inside-its-execution