【问题标题】:Using a lambda/std::function to delete the object that owns the function使用 lambda/std::function 删除拥有该函数的对象
【发布时间】:2018-04-07 17:49:50
【问题描述】:

我有一个 Layer 类,其中包含某些对象,如按钮、滑块等。我已经构建了 Layer 类,以便它能够自我刷新。本质上,它会删除所有/大部分对象,然后重新创建它们。原因是我有各种功能,比如更改字体大小或 UI 的主题,我希望这些更改立即显而易见。

始终如一地完成此任务的唯一方法是简单地重新构建所有内容,因为主题和字体大小会影响所有内容的外观和布局。这一切都运作良好,我对系统感到满意。

但是,当属于该层的对象启动刷新时,就会出现问题(我所说的问题是指崩溃)。例如,我有一个字体大小滑块,它应该更改字体大小,然后刷新图层。它有一个对层刷新函数的回调,由 lambda 捕获,并存储在成员 std::function 对象中。当函数执行时会发生什么,在调用函数完成之前的某个时刻,滑块当然会被破坏并与它一起破坏函数。因此,当前正在运行的函数停止存在并且程序崩溃。

我的问题是,在这种矛盾的情况下,实现这种行为不会导致崩溃的良好而优雅的方法是什么?

编辑:代码

class MySlider : public MyNode
{
    //... various other class members

    std::function<void()> functionOnRelease = nullptr;

    MySlider::MySlider(/*constructor arguments*/)
    {
        this->preferenceKey = key;

        slider->addTouchEventListener([=](Ref* sender, ui::Widget::TouchEventType type)
        {
            if (type == ui::Widget::TouchEventType::ENDED)
                on_release();
        });

        //... remainder of constructor body
    }

    void MySlider::set_function_on_release(const std::function<void()>& functionOnRelease)
    {
        this->functionOnRelease = functionOnRelease;
    }

    void on_release()
    {
        if (preferenceKey.empty() == false)
            PREFM->set_preference(preferenceKey, slider->getPercent() + min);

        if (functionOnRelease)
            functionOnRelease();

        //the program crashes at this point when the slider gets destroyed
    }
}


//The layer in question and the function that creates its slider
void Options::create_font_size_slider()
{
    NodeVector sliders;

    //there's a static create function convention imposed by the engine    
    auto fontSizeSlider = MySlider::createWithPreferenceAutomatic("Font Size: ", "FontSizePercentage", 50, 150, false, sliders);

    fontSizeSlider->set_function_on_release([=] { this->refresh(); /*this kills the slider*/});
}

编辑2:

我已经设法通过一个简单而丑陋的 hack 来避免崩溃。在稍有延迟(0.05 秒)后,我已强制 refresh() 函数在单独的线程上发生。这为所有事情的发生提供了足够的时间,而不会出现明显的延迟。如果设备真的很慢和奇怪的线程调度,我可以忍受潜在的偶尔崩溃,因为需要完成的所有事情都在崩溃发生之前完成。

【问题讨论】:

  • 重新创建每个对象只是为了刷新似乎有点过分。你不能在每个对象上使用类似update() 的函数吗?
  • 如果没有设想更改会影响整个 UI,我可以。例如,如果我更改主题,新主题将具有不同的字体,并且可以具有不同的精灵大小。这意味着所有对象的大小也必须更新,这也意味着它们的相对位置需要更新。此外,有许多不同的类以不同的方式运行,因此我必须单独定制所有更新功能。理论上,我可以构建这样一个系统,但它会非常复杂且容易出错。重建一切可以完美地完成工作。
  • 我不明白你的意思是滑块破坏了它的功能。我认为您需要向我们展示相关代码。在我看来,刷新/重新创建整个层的功能以某种方式归层中包含的控件类之一所有,这似乎不太合乎逻辑。
  • 我基本上只会在除名称之外的所有内容中重新创建当前行为,唯一的好处是对象永远不会停止存在。它可以解决函数的这个特殊问题,但执行的工作量几乎相同,而且我需要维护大量新代码。
  • 函数本身不属于对象。该对象以 std::function 的形式拥有一个指向它的指针。具体来说,函数调用被包装在一个 lambda 中,该 lambda 也捕获了层的指针。这个 lambda 被存储在 std::function 中。当对象被销毁时,被调用并导致崩溃的 std::function 也会被销毁。我现在将在问题中发布一些代码。

标签: c++ lambda


【解决方案1】:

您可以通过对对象进行引用计数来解决这个问题(例如 COM 所做的)。基本上:

Retain () 增加对象的引用计数。

Release () 减少对象的引用计数,如果现在为零,则将其删除。

一个对象从引用计数为 1 开始,您永远不会显式删除引用计数的对象(保护析构函数)。

现在,当一个对象想要触发刷新时,它可以先调用自身的 Retain。当控制权返回给对象时,它可以调用Release然后立即返回。对 Release 的调用可能会删除该对象,但这并不重要,只要它随后不触及其实例数据即可。

引用计数可以解决很多对象生命周期问题,您可以致电delete (this)。娱乐时间。另见:

http://en.cppreference.com/w/cpp/memory/shared_ptr

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-13
    • 2018-11-12
    • 2013-12-15
    • 1970-01-01
    • 2016-03-27
    • 2014-08-27
    • 1970-01-01
    相关资源
    最近更新 更多