Re-assigning an std::function object while inside its execution

大兔子大兔子 提交于 2020-08-09 05:21:28

问题


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), where f 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!