如何将本地引用变量传递给lambda函数,但是当其关闭已经完成时将调用该函数? [英] How to pass a local referenced variable to a lambda function, but the function will be called when its closure is already finished?

查看:240
本文介绍了如何将本地引用变量传递给lambda函数,但是当其关闭已经完成时将调用该函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

下面的代码应该打印两个相同的整数,但不是这样。在JavaScript中的类似程序将打印两个相同的数字。



在C ++中似乎是合理的,因为当 stdfun 已执行, regfun 已完成,那时 local_var 不再存在。



所以我的问题是我们如何正确地访问捕获的局部变量超过其上下文的生命周期,如JavaScript默认情况下?

  #include< functional> 
#include< future>
#include< cmath>
#include< iostream>
#include< ctime>
#include< windows.h>
using namespace std;

int start_stdfun_in_a_new_thread(std :: function< void()> stdfun)
{
int id = rand
std :: function< void()> call = [=]()
{
Sleep(1000); // let regfun finish
stdfun();
};
std :: async(std :: launch :: async,call);
return id;
}

void regfun()
{
int local_var = -1;
std :: function< void()> stdfun = [=,& local_var]()mutable - > void
{
cout<<< local_var<< endl;
};
local_var = start_stdfun_in_a_new_thread(stdfun);
cout<< local_var<< endl;
}

int main()
{
regfun();
Sleep(1000000);
}

很难描述我的问题是什么,在c ++中的东西像我们在javascript中做的。如果你非常熟悉javascript,也许你可以理解我的意思。

解决方案

这里是一个C ++版本的代码



作为一个快乐的副作用,它也表现出你想要的行为:

  int start_stdfun_in_a_new_thread(std :: function< void()> stdfun)
{
int id = rand
//不需要这是被擦除的类型`std :: function`:
auto call = [=]()
{
Sleep(1000); // let regfun finish
stdfun();
};
std :: async(std :: launch :: async,call);
return id;
}

void regfun()
{
//这里我们创建一个指向int的共享指针并将其存储在本地:
auto local_var = std :: make_shared< int>( - 1);
//我们获取共享指针并将其复制到我们的lambda:
//注意,我们只需要键入erase(将其转换为std ::函数)当我们需要,
//没有更早。
auto stdfun = [local_var]()mutable - > void
{
cout<< * local_var<< endl;
};
//移动只是一个优化。它工作与否。
* local_var = start_stdfun_in_a_new_thread(std :: move(stdfun));
cout<< * local_var<< endl;
}

int main()
{
regfun();
Sleep(1000000);
}

C ++中的数据生命周期相对简单, > 1 。变量在自动存储中创建它们的数据,当变量超出范围时,数据也被回收。对当前语句的长度存在未命名的数据(临时)(直到; ),但在某些情况下可以将其生命周期扩展到附近的引用变量。然而,这不是暂时的:因此在任何情况下,未命名的对象比在其中创建的 {} 封闭块更长。 2 p>

您也可以通过 new (或通过其他方法,如 malloc )。这样的数据有一个更复杂的生命周期,但通常直到某些地方的代码我完成了它明确。



std :: shared_ptr 是一个类,用于给您的数据更复杂的生命周期。您可以在 std :: shared_ptr 中存储通过 new 创建的数据,但更常规的方法是使用 std :: make_shared< TYPE >(构造参数创建它(这在大多数情况下,比其他方法更安全和更高效。)



每个 std :: shared_ptr 变量具有常规的生命周期,然而其中的数据具有由 shared_ptr 的最后一个 c $ c>指向它。 (我说 copy ,因为这不是神奇的:如果你必须将一个数据与 shared_ptr 无关,无关他们的共同数据,并且都认为他们拥有它。)



所以你可以创建一个 std :: shared_ptr< int> / code>并通过它的值,它指向的数据将存活,只要任何指针。当最后一个指针离开时,数据被清除。



这是我上面做的。 local_var 指向的生命周期自动扩展为 regfun stdfun 及其副本(包括将存在于 std :: function 中的副本)的生命周期。



我最常使用的最后一种技术是使用 auto auto 是一种创建变量的方法,它从变量初始化方式获取类型。对于lambdas,这允许您直接存储它们(而不是类型擦除 std :: function ),因为不能说出lambda的名称。



在其他情况下,右侧已经详细说明了所涉及的类型( std :: make_shared< int> ,并且在左侧重复它不能增加清晰度,并违反DRY(不要重复自己)原则。



我使用 std :: function 谨慎,因为 std :: function 主要是关于类型擦除。类型擦除是采取具有所有细节的类型,并将其包装在定制的盒子中的动作,其擦除所有这些细节并留下统一的运行时接口。虽然这是酷和所有,它带有一个运行时(有时编译时间)的成本。你应该从事类型擦除(如 std :: function )sparingly:在实现被隐藏的接口中,当你想要处理多个不同类型(如函数指针,和多种不同的lambdas),并以统一的方式存储它们,或者当你想使用lambdas,并且会由于你无法说出他们的名字而感到不满。






1 C ++中的数据生命周期很简单,除了几个角点。在这些角落的情况下,生命可以变得非常复杂。这些角落中最简单的情况是当直接绑定到引用时的临时生命期延长。其他有趣的角色案例包括安全派生指针和严格混叠的概念,当你弄乱指针的类型和位时,这些概念会发挥作用。其他复杂的情况包括 static 局部变量, static 和非静态全局变量以及线程局部变量,复制elision 2.1 。我可以给你最基本的建议是简单地避免这些大多数情况,如果你发现你真的需要使用它们,花一大段时间阅读隐含的生活和各种常见的陷阱和错误发生。生命周期问题是非常复杂的,我试过它,它的工作不是强有力的证据,您的代码是正确的 - 未定义,实现指定,或极脆弱的行为是很容易触发所有这些情况下。



2 这句话是谎言。 2.1 复制elision可能导致一个未命名的局部变量的生命周期远远超过封闭块,但只是因为它在概念上变成其他命名或未命名的变量,并且复制构造函数/析构函数, )。但是,这是一个有用的谎言,因为它有助于阻止思考一些真正常见的参考生命延长的误解。


The code below should print two same integers, but that's not the case. An analogous program in JavaScript would print two same numbers.

It seems reasonable in C++, because when the stdfun is executed, regfun already finished, the local_var doesn't exist anymore at that time.

So my question is how can we correctly access the captured local variable beyond the lifetime of its context like JavaScript does per default?

#include <functional>
#include <future>
#include <cmath>
#include <iostream>
#include <ctime>
#include <windows.h>
using namespace std;

int start_stdfun_in_a_new_thread(std::function<void()> stdfun)
{   
    int id = rand();
    std::function<void()> call = [=]()
    {
        Sleep(1000);//let regfun finish 
        stdfun();
    };
    std::async(std::launch::async,call);
    return id;
}

void regfun()
{
    int local_var = -1;
    std::function<void()> stdfun = [=,&local_var]() mutable -> void
    {
        cout<<local_var<<endl;
    };
    local_var = start_stdfun_in_a_new_thread(stdfun);
    cout<<local_var<<endl;
}

int main() 
{
    regfun();
    Sleep(1000000);
}

It is so hard to describe what my question really is, but I just need something in c++ like what we do in javascript. If you are very famillar with javascript, maybe you can understand what I fully mean.

解决方案

Here is a C++ version of your code that no longer does undefined behavior.

As a happy side effect, it also exhibits the behavior you want:

int start_stdfun_in_a_new_thread(std::function<void()> stdfun)
{   
  int id = rand();
  // no need for this to be a type erased `std::function`:
  auto call = [=]()
  {
      Sleep(1000);//let regfun finish 
      stdfun();
  };
  std::async(std::launch::async,call);
  return id;
}

void regfun()
{
  // here we create a shared pointer to an int and store it locally:
  auto local_var = std::make_shared<int>(-1);
  // we take the shared pointer and copy it by value into our lambda:
  // note that we only type erase (turn it into a std::function) when we need to,
  // no earlier.
  auto stdfun = [local_var]() mutable -> void
  {
    cout<<*local_var<<endl;
  };
  // the move is just an optimization.  It works with or without it.
  *local_var = start_stdfun_in_a_new_thread(std::move(stdfun));
  cout<<*local_var<<endl;
}

int main() 
{
  regfun();
  Sleep(1000000);
}

Lifetime of data in C++ is relatively simple, barring a few corner cases1. Variables create their data in automatic storage, and when the variable goes out of scope the data is also recycled. Unnamed data (temporaries) exist for the length of the current statement (until the ;), but under certain circumstances can have their lifetime extended to that of a nearby reference variable. This, however, is not transitory: so under no circumstance do unnamed objects ever outlast the {} enclosed block they are created in.2

You can also create data on the free store via new (or via other methods, like malloc). Such data has a more complicated lifetime, but generally until some code somewhere says "I am done with it" explicitly.

std::shared_ptr is a class used to give your data a more complicated lifetime. You can store data created via new in a std::shared_ptr, but the more conventional way is to use std::make_shared<TYPE>( construction arguments ) to create it (this is, under most circumstances, both safer and more efficient than the other method).

Each std::shared_ptr variable has a conventional lifetime, however the data it points to has a lifetime bounded by the last copy of the shared_ptr that points to it. (I say copy, because this isn't magic: if you have to unrelated shared_ptr to one piece of data, they will (usually) be clueless about their common data, and both think they "own" it).

So you can create a std::shared_ptr<int> and pass it around by value, and the data it points to will live as long as any of the pointers do. When the last pointer goes away, the data is cleaned up.

Which is what I did above. The lifetime of what local_var points to is automatically extended to be both the body of regfun, and the lifetime of stdfun and copies thereof (including the copy that will live in a std::function).

The final technique I used profusely was the use of auto. auto is a way of creating variables that gets it type from how the variable is initialized. For lambdas, this lets you store them directly (instead of as a type-erased std::function), as the name of a lambda cannot be uttered.

In other cases, the right hand side already details the type involved (std::make_shared<int> makes the type clear), and repeating it on the left hand side fails to add much clarity, and violates the DRY (don't repeat yourself) principle.

I use std::function sparingly, as std::function is mainly about type erasure. Type erasure is the act of taking a type with all its details, and wrapping it up in a custom crafted box that erases all of those details and leaves a uniform run time interface. While this is cool and all, it comes with a run time (and sometimes compile time) cost. You should engage in type erasure (like std::function) sparingly: in interfaces where the implementation is hidden, when you want to treat multiple different types (such as function pointers, and multiple different lambdas) in a uniform way and store them, or when you want to use lambdas and would be annoyed by your inability to utter their name.


1 Lifetime of data in C++ is simple, barring a few corner cases. In those corner cases, lifetime can get extremely complex. The most simple of these corner cases is temporary lifetime extension when bound directly to a reference. Other fun corner cases includes the concept of "safely derived pointers" and "strict aliasing", which come into play when you mess around with the types and bits of pointers. Other complex corner cases include static local variables, static and non-static global variables, and thread-local variables, and copy elision2.1. The most basic advice I can give you would be to simply avoid most of these corner cases, and if you find you really really need to use them, spend a whole bunch of time reading up on the implied lifetime and the various common traps and errors that occur. The lifetime issues are sufficiently complex that "I tried it and it worked" is not strong evidence that your code is correct -- undefined, implementation specified, or extremely fragile behavior is ridiculously easy to trigger in all of these cases.

2 This sentence is a lie. 2.1Copy elision can cause an unnamed local variable to have a lifetime far longer than the enclosing block, but only because it conceptually became other named or unnamed variables, and the copy constructor/destructors where all elided (eliminated). However, it is a useful lie, as it helps block thinking about some really common "reference lifetime extension" misconceptions.

这篇关于如何将本地引用变量传递给lambda函数,但是当其关闭已经完成时将调用该函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆