重新分配std::在其执行过程中,函数对象。

时间:2021-09-08 18:03:12

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&operator =(f&f);到20.8.11.2 /18,它的影响是什么?

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), where f 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&operator =(f&f);到20.8.11.2 /18,它的影响是什么?

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), where f 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的副本而不是指向它的指针,该怎么办?)