在用作插槽的lambda函数中捕获的堆栈分配变量的寿命是多少? [英] What is the lifespan of the captured stack allocated variables in lambda functions used as slots?

查看:42
本文介绍了在用作插槽的lambda函数中捕获的堆栈分配变量的寿命是多少?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要帮助来了解lambda函数的工作方式,以防止使用它们时发生内存泄漏.更具体地说,我想知道 foo 在以下情况下何时会被破坏:

I need help understanding the way lambda functions work in order to prevent memory leaks when using them. More specifically, I would like to know when foo will be destroyed in the following case:

void MainWindow::onButtonClicked()
{
    QTimer *t(new QTimer(this));
    bool foo = false;

    t->setSingleShot(true);
    t->setInterval(1000);
    t->start();

    connect(t, &QTimer::timeout, [=](){
        delete t;
        qDebug() << foo;
    });
}

使用 [&] 会怎样?

推荐答案

经评估的lambda表达式是函子实例.函子是具有 operator()的对象.捕获的变量是该函子对象的成员.它们的生存期不会因其类型而改变.因此,无论您捕获引用还是值,它们的生存期都是相同的.确保引用有效是您的工作-也就是说,引用的对象未被销毁.

An evaluated lambda expression is a functor instance. A functor is an object with operator(). The captured variables are members of that functor object. Their lifetime doesn't change based on their type. Thus, whether you capture references or values, their lifetime is the same. It's your job to ensure that the references are valid - i.e. that the objects they reference haven't been destroyed.

函子的生存期与连接的生存期相同.连接以及函子将一直持续到以下任一情况:

The functor's lifetime is the same as the connection's lifetime. The connection, and thus the functor, will last until either:

    QObject :: connect()

QObject :: disconnect()

  • QObject 的生​​命结束,并且其析构函数被调用.

    The QObject's life ends and its destructor is invoked.

    考虑到上述情况,按引用捕获局部变量的唯一有效用法是当局部变量的寿命超过连接时.一些有效的例子是:

    Taking the above into account, the only valid use of local variable capture-by-reference is when the local variables outlive the connection. Some valid examples would be:

    void test1() {
      int a = 5;
      QObject b;
      QObject:connect(&b, &QObject::destroyed, [&a]{ qDebug() << a; });
      // a outlives the connection - per C++ semantics `b` is destroyed before `a` 
    }
    

    或者:

    int main(int argc, char **argv) {
      QObject * focusObject = {};
      QApplication app(argc, argv);
      QObject * connect(&app, &QGuiApplication::focusObjectChanged,
                        [&](QObject * obj){ focusObject = obj; });
      //...
      return app.exec();  // focusObject outlives the connection too
    }
    

    您问题中的代码不必要地复杂.无需手动管理此类计时器:

    The code in your question is needlessly complex. There's no need to manage such timers manually:

    void MainWindow::onButtonClicked() {
      bool foo = {};
      QTimer::singleShot(1000, this, [this, foo]{ qDebug() << foo; });
    }
    

    这里重要的部分是提供对象上下文( this )作为 singleShot 的第二个参数.这样可以确保 this 必须比函子寿命更长.相反,函子将在 this 被销毁之前被销毁.

    The important part here is to provide the object context (this) as the 2nd argument of singleShot. This ensures that this must outlive the functor. Conversely, the functor will be destroyed before this is destroyed.

    假设您确实想实例化一个新的瞬态计时器,则删除与该信号相连的插槽中的信号源对象是不确定的行为.您必须改为将删除操作推迟到事件循环中:

    Assuming that you really wanted to instantiate a new transient timer, it is undefined behavior to delete the signal's source object in a slot connected to such a signal. You must defer the deletion to the event loop instead:

    void MainWindow::onButtonClicked()
    {
      auto t = new QTimer(this);
      bool foo = {};
    
      t->setSingleShot(true);
      t->setInterval(1000);
      t->start();
    
      connect(t, &QTimer::timeout, [=](){
        qDebug() << foo;
        t->deleteLater();
      });
    }
    

    t foo 都被复制到functor对象中.lambda表达式是一种记号的简写-您可以自己明确地编写它:

    Both t and foo are copied into the functor object. The lambda expression is a notational shorthand - you could write it explicitly yourself:

    class $OpaqueType {
      QTimer * const t;
      bool const foo;
    public:
      $OpaqueType(QTimer * t, bool foo) :
        t(t), foo(foo) {}
      void operator()() {
        qDebug() << foo;
        t->deleteLater();
      }
    };
    
    void MainWindow::onButtonClicked() {
      //...
      connect(t, &QTimer::timeout, $OpaqueType(t, foo));
    }
    

    由于一个lambda实例只是一个对象,因此可以肯定地可以将其分配给一个变量,并在多个信号需要连接到同一lambda的情况下摆脱代码重复:

    Since a lambda instance is just an object, you can certainly assign it to a variable and get rid of code duplication should more than one signal need to connect to same lambda:

    auto f = [&]{ /* code */ };
    connect(o, &Class::signal1, this, f);
    connect(p, &Class::signal2, this, f);
    

    lambda的类型是唯一的,不可说的(也称为不透明的)类型.您不能从字面上提及它-语言没有机制可以这样做.您只能通过 decltype 引用它.这里的 decltype he中的 he ,他不会被命名.C ++人士只是在开玩笑而已,无论他们是否故意.否则我不会被说服.

    The type of the lambda is a unique, unutterable, also called opaque, type. You cannot mention it literally - there's no mechanism in the language to do so. You can refer to it via decltype only. Here decltype is the he in he who shall not be named. C++ people just worked in a Harry Potter joke, whether they meant to or not. I won't be convinced otherwise.

    这篇关于在用作插槽的lambda函数中捕获的堆栈分配变量的寿命是多少?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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