如何管理捕获的 lambda 输入的内存?

问题描述 投票:0回答:2

所以在现代 C++ 中,我们使用智能指针来确保共享资源在我们使用之前不会被删除。

lambda 回调是否有 std::function<> 的等价物? 在传递特定签名的函数而不是整个类或接口的地方,一般委托注入。

问题示例代码:

struct Foo{
    int i = 0;
    void addInt(int input){
        i += input;
    }
};

int main(int argc, char* args[]){
    Foo* lostMemory = new Foo();
    std::function<void(int)> lambda = [lostMemory](int b) { lostMemory->addInt(b); };

    //run memory good
    lambda(8);

    //run memory deleted - should crash right?
    delete lostMemory;
    lambda(8);
    return 0;
}

https://godbolt.org/z/qsK65ozGG

它没有崩溃的事实不是问题或问题

问题:像这样创建 std::functional 的正确方法是什么,资源可能会被删除?

注意:在一个示例中,lostMemory 将是传递自身的类的实例,并且不能仅更改为 std::shared_ptr<>。堆栈溢出简化使它看起来像是答案。

在下面回答了我如何处理它的问题。

NOT 使用 std::enable_shared_from_this() 因为它充满了危险的设置问题和正确使用的边缘情况

c++ lambda raii
2个回答
1
投票

你为什么不像你提到的那样只使用智能指针?

#include <functional>
#include <memory>

struct Foo{
    int i = 0;
    void addInt(int input){
        i += input;
    }
};

int main(int argc, char* args[]){
    std::shared_ptr<Foo> lostMemory( new Foo() );
    std::function<void(int)> lambda = [lostMemory](int b) { lostMemory->addInt(b); };

    //run memory good
    lambda(8);

    //run memory deleted - should crash right?
    lostMemory.reset();
    lambda(8);
    return 0;
}

看,lambda 只是幕后的匿名函子。如果你将上面的代码粘贴到 cppinsights.io 上,你将得到类似这样的东西(稍微清理一下):

#include <functional>
#include <memory>

struct Foo
{
  int i = 0;
  inline void addInt(int input)
  {
    this->i = this->i + input;
  }
};

int main(int argc, char ** args)
{
  std::shared_ptr<Foo> lostMemory = std::shared_ptr<Foo>(new Foo());
    
  class __lambda_13_39
  {
    public: 
    inline /*constexpr */ void operator()(int b) const
    {
      static_cast<const std::__shared_ptr_access<Foo, 2, false, false>&>(lostMemory).operator->()->addInt(b);
    }
    
    private: 
    std::shared_ptr<Foo> lostMemory;
    public: 
    __lambda_13_39(const std::shared_ptr<Foo> & _lostMemory)
    : lostMemory{_lostMemory}
    {}
    
  };
  
  std::function<void (int)> lambda = std::function<void (int)>(__lambda_13_39{lostMemory});
  lambda.operator()(8);
  static_cast<std::__shared_ptr<Foo, 2>&>(lostMemory).reset();
  lambda.operator()(8);
  return 0;
}

你可以看到仿函数持有一个

std::shared_ptr<Foo>
作为成员。这正是 lambda 也会做的事情。

或者,对于更进化的情况,使用函子本身而不是 lambda。


0
投票

所以在评论讨论之后我想出了一个适合我的解决方案。它绝对不完美,开发人员可能会错误地使用它,但它比我拥有的要好。

所以第 1 步:我没有传递原始函数,而是创建了一个结构,该结构将具有一个可以检查的 weak_ptr<>。我们需要将 this 绑定到拥有该函数的对象,但我们稍后会谈到。

template<typename _aliveToken, class _args>
class SharedFunction{
public:
    SharedFunction(const std::weak_ptr<_aliveToken>& aliveToken, const std::function<void(_args)>& function):
        m_aliveToken(aliveToken),
        m_function(function)
    {
        
    }

    std::weak_ptr<_aliveToken> m_aliveToken;
    std::function<void(_args)> m_function;
};

在我的代码中,我将其链接到回调容器中,但为此我们将其保留在单个“安全”函数中,既美观又简单。模板只是阻止我们绑定到单个指针类型或输入值。

首先我们必须在我们的 foo 类中创建一个锁对象。我们不能让 foo 的实例有一个指向自身的智能指针,但它可以包含一个在它被删除时消失的智能指针

struct Foo {
    std::shared_ptr<char> lock;
    int i = 0;

    Foo() : lock(std::make_shared<char>(0)){
}

    void addInt(int input) {
        i += input;
    }
};

接下来我们需要检查我们新的“安全”功能的功能,以确保我们仍然可以使用它

void checkObjectFirst(SharedFunction<char, int>& function, int value)
{
    const auto isSafe = function.m_aliveToken.lock();
    if(isSafe){
        function.m_function(value);
    }
}

现在我们的代码知道一旦所有者不再活着就不再调用那个 lambda

int main(int argc, char* args[])
{
Foo* lostMemory = new Foo();

    auto lambda = [lostMemory](int b)  { lostMemory->addInt(b); };
    SharedFunction<char, int> function(lostMemory->lock, lambda);

    //run memory good
    checkObjectFirst(function, 8);

    //run memory deleted - we're now good!
    delete lostMemory;
    checkObjectFirst(function, 8);
    return 0;
}
© www.soinside.com 2019 - 2024. All rights reserved.