Search code examples
c++c++11lambdashared-ptr

c++: Efficient way to passing shared_ptr to a lambda


I've created a small program years ago, and I've recently stared to use smart pointers. So for practice reasons I refactored some part of the code based on Scott Meyers - Effective Modern C++ book.

Imagine a class, that holds a member shared_ptr of another class, that manages the life cycle of some other unnecessary objects.

class Foo {
...
    std::shared_ptr<Bar> manager;

    void DoSomeWork();
}

The implementation of the DoSomeWork method contains a lambda collectIfNeighbor, because it is used frequently in some parts, and this was the easiest way to achieve the correct behavior. The main idea was to capture only what is needed in the lambda and use smart pointers instead of nullptr checks everywhere.

void Foo::DoSomeWork(){
    std::vector<Object*> neighbors;
    auto collectIfNeighbor = [this, &neighbors](const Position& pos) {
        ...    
        if ( *some condition* )
            neighbors.push_back(manager->GetObject(pos));
    };
    collectIfNeighbor(Position(1,1));    // example usage
    ...
}

So the problem here is that inside the lambda I capture this, but I don't really need the whole, just the manager. What is the most efficient way to pass a shared_ptr to a lambda?

If possible I do not want to pass the whole this because I use only one member. Either I do not want to create and destroy tons of shared_ptrs, because it would make it slower. Also, it would be nice not to care about manager is still alive or not.

There were other approaches to the lambda implementation. Copy the shared_ptr to a local one, and passing it by reference. But I make and additional copy locally, and I'm afraid every time when the lambda called. Another approach was to simply create a regular pointer and passing that to the lambda.

std::shared_ptr<Bar> localManager = manager;
auto lambdaV1= [&localManager, &neighbors](const Position& pos) {
    ...    
    if ( *some condition* )
        neighbors.push_back(manager->GetObject(pos));
};
Bar* localManagerPtr = manager.get();
auto lambdaV2= [&localManagerPtr , &neighbors](const Position& pos) {
    ...    
    if ( *some condition* && manager != nullptr)    // extra check on manager
        neighbors.push_back(manager->GetObject(pos));
};

I checked Passing shared_ptr to lambda cause memory leak and Capture by universal ref however it didn't helped me much.


Solution

  • For situations like this one, I always recommend to be clear about this question first: Can my lambda legally be called after the lifetime of this? If so, capturing this would not be the way to go here. A similar question is then: Is my lambda allowed to extend the lifetime of this? If so, capturing this and the according shared_from_this approach via a shared pointer of this is a common scheme but always with the drawback of the additional copy (and the possible unclear/complex life time of this...).

    Under the premise, that you are totally clear about these questions and assuming here, that the lambda must/can not be called after the lifetime of this, simply capturing this is the simplest and cheapest solution as NathanOliver stated with the additional hint for you, that a legal this-pointer is expected for any calls to your lambda. For only one parameter to be captured, the simple raw pointer capturing solution by largest_prime is sometimes a bit more explicit or at least a bit clearer (not such a hammer like this-capture) if no other members or member methods are required within the body of your lambda, i.e. maybe the lambda's work is semantically a bit decoupled from your actual class and might be used elsewhere further on where your class is not part of the context by design.