Search code examples
c++lambdaraii

How to manage memory for captured inputs of a lambda?


So in modern C++ we use smart pointers to make sure that a shared resource didn't get deleted before we go to use it.

Is there an equivalent for std::function<> for lambda callbacks? Where you pass out a function of a certain signature rather than a whole class or interface, general delegate injection.

Example code of problem:

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

The fact it didn't crash isn't the problem or question

Question: What is the right way to create a std::functional like this, where the resource may be deleted?

Note: in an example where lostMemory will be an instance of a class passing itself, and can't just be changed to a std::shared_ptr<>. Stack overflow simplification makes it look like that is the answer.


Solution

  • Why don't you just use smart pointers as you mentioned?

    
    #include <functional>
    #include <memory>
    #include <iostream>
    
    struct Foo{
        Foo() {
            std::cout << "Foo created" << std::endl;
        }
        ~Foo() {
            std::cout << "Foo destroyed" << std::endl;
        }
        int i = 0;
        void addInt(int input){
            i += input;
        }
    };
    
    int main(int argc, char* args[]){
        std::cout << "-- Start of program" << std::endl;
        std::shared_ptr<Foo> lostMemory( new Foo() );
        std::weak_ptr<Foo> weak = lostMemory;
        std::cout << "-- Before lambda" << std::endl;
        std::function<void(int)> lambda = [weak](int b) { 
            if ( auto sp = weak.lock() ) { 
                std::cout << "Added int" << std::endl;
                sp->addInt(b);
            } 
            else std::cout << "Not doing this" << std::endl;
        };
        std::cout << "-- After lambda" << std::endl;
        lambda(8);
        std::cout << "-- Before deleting" << std::endl;
        lostMemory.reset();
        std::cout << "-- After lambda" << std::endl;
        lambda(8);
        std::cout << "-- The end" << std::endl;
        return 0;
    }
    

    Produces

    Program stdout
    -- Start of program
    Foo created
    -- Before lambda
    -- After lambda
    Added int
    -- Before deleting
    Foo destroyed
    -- After lambda
    Not doing this
    -- The end
    

    Godbolt link: https://godbolt.org/z/ceMevznhv

    See, lambdas are mere anonymous functors under the hood. If you paste the above code on cppinsights.io you will have something like this (cleaned up a bit):

    #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;
    }
    

    You can see that the functor holds an std::shared_ptr<Foo> as a member. That is exactly what the lambda will do as well.

    Alternatively, use a functor itself instead of a lambda for cases that are a bit more evolving.