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?
我有一个std::函数对象,我将它用作某个事件的回调函数。我给这个对象分配一个lambda,在这个对象中,我将对象分配给一个不同的lambda mid执行。当我这样做时,我得到了一个segfault。这不是我可以做的吗?如果是这样,为什么?我该如何实现这个目标呢?
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();
2 个解决方案
#1
2
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.
标准在std的操作中没有指定::函数::操作符()函数使用它的内部状态对象。在实践中,一些实现在调用之后使用它。
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
.
现在,如果您想要更改在bob()内调用bob时发生的事情,只需设置next_task。
#2
0
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.
这取决于在(re)赋值之后,被调用的lambda是否访问它的任何非静态数据成员。如果这样做,你就会得到未定义的行为。否则,我相信不会有什么坏事发生。
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
.
在OP的例子中,一个lambda对象——用l_1表示,它由std::函数对象被调用,在执行过程中,std::函数对象被分配给另一个lambda——表示为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
.
当f与l_2和*结合时,这是std::被分配给的函数对象。此时,临时std::函数保存l_2,并且*这个保持l_1。交换之后,临时持有l_1和*,它持有l_2(*)。然后临时被销毁,l_1也被销毁。
In summary, while running operator()
on l_1
this object gets destroyed. Then according to 12.7/1
总之,在l_1上运行操作符()时,该对象将被销毁。然后根据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.
Lambdas非静态数据成员对应它的捕获。如果你不访问它们,它就会很好。
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
有一个更重要的观点是由雅克的回答引起的。据我所知,关注的是std::函数::操作符(),在将调用转发到l_1之后,尝试访问l_1(现在已经死亡)了吗?我不认为这是事实,因为std的作用:::操作符()不暗示。实际上,20.8.11.2.4说这个调用的效果是。
INVOKE(f, std::forward<ArgTypes>(args)..., R)
(20.8.2), wheref
is the target object (20.8.1) of*this
.调用(f,std::转发< ArgTypes >(args)…,R) (20.8.2), f是*的目标对象(20.8.1)。
which basicallky says that std::function::operator()
calls l_1.operator()
and does nothing else (at least, nothing that is detectable).
其中basicallky说std:::运算符()调用l_1.operator(),不做任何其他操作(至少,没有检测到的东西)。
(*) 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?)
(*)我正在详细说明交换是如何在地毯下发生的,但这个想法仍然有效。(例如,如果临时持有的是l_1的副本而不是指向它的指针,该怎么办?)
#1
2
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.
标准在std的操作中没有指定::函数::操作符()函数使用它的内部状态对象。在实践中,一些实现在调用之后使用它。
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
.
现在,如果您想要更改在bob()内调用bob时发生的事情,只需设置next_task。
#2
0
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.
这取决于在(re)赋值之后,被调用的lambda是否访问它的任何非静态数据成员。如果这样做,你就会得到未定义的行为。否则,我相信不会有什么坏事发生。
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
.
在OP的例子中,一个lambda对象——用l_1表示,它由std::函数对象被调用,在执行过程中,std::函数对象被分配给另一个lambda——表示为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
.
当f与l_2和*结合时,这是std::被分配给的函数对象。此时,临时std::函数保存l_2,并且*这个保持l_1。交换之后,临时持有l_1和*,它持有l_2(*)。然后临时被销毁,l_1也被销毁。
In summary, while running operator()
on l_1
this object gets destroyed. Then according to 12.7/1
总之,在l_1上运行操作符()时,该对象将被销毁。然后根据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.
Lambdas非静态数据成员对应它的捕获。如果你不访问它们,它就会很好。
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
有一个更重要的观点是由雅克的回答引起的。据我所知,关注的是std::函数::操作符(),在将调用转发到l_1之后,尝试访问l_1(现在已经死亡)了吗?我不认为这是事实,因为std的作用:::操作符()不暗示。实际上,20.8.11.2.4说这个调用的效果是。
INVOKE(f, std::forward<ArgTypes>(args)..., R)
(20.8.2), wheref
is the target object (20.8.1) of*this
.调用(f,std::转发< ArgTypes >(args)…,R) (20.8.2), f是*的目标对象(20.8.1)。
which basicallky says that std::function::operator()
calls l_1.operator()
and does nothing else (at least, nothing that is detectable).
其中basicallky说std:::运算符()调用l_1.operator(),不做任何其他操作(至少,没有检测到的东西)。
(*) 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?)
(*)我正在详细说明交换是如何在地毯下发生的,但这个想法仍然有效。(例如,如果临时持有的是l_1的副本而不是指向它的指针,该怎么办?)