How do I capture a smart pointer in a lambda?

c++, lambda, smart-pointers

Solution

A lambda (even one with a universal capture like `[=]`) only actually captures variables used within its definition. Since in your example, `pointer` is never used inside the lambda, it's not captured and thus when it goes out of scope, it's the last shared pointer referring to `stored` and `free()` is called.

If you want to capture `pointer`, you could force its use:

return [=](const char * delim) mutable -> const char *
{
  pointer;
  const char *b = strtok_r(stored, delim, &tmpstorage);
  stored = nullptr;
  return b;
};

However, this is rather hackish. You want your functor stateful and with nontrivial state management. To me, this is a strong indicator an actual named class (instead of a lambda) would be in order. So I would change it like this:

std::function<const char *(const char *)> test(const char *input)
{
  struct Tokenizer
  {
    std::shared_ptr<char> pointer;
    char* stored;
    char* tmpstorage;
    explicit Tokenizer(char* stored) : pointer(stored, free), stored(stored), tmpstorage(nullptr) {}
    const char* operator() (const char * delim)
    {
      const char *b = strtok_r(stored, delim, &tmpstorage);
      stored = nullptr;
      return b;
    }
  };
  return Tokenizer(strdup(input));
}

Problem

What is best way to capture a smart pointer in a lambda? One attempt of mine lead to a use-after-free bug. Example code: ``` #include <cstring> #include <functional> #include <memory> #include <iostream> std::function<const char *(const char *)> test(const char *input); int main() { std::cout.sync_with_stdio(false); std::function<const char *(const char *)> a = test("I love you"); const char *c; while ((c = a(" "))){ std::cout << c << std::endl; } return 0; } std::function<const char *(const char *)> test(const char *input) { char* stored = strdup(input); char *tmpstorage = nullptr; std::shared_ptr<char> pointer = std::shared_ptr<char>(stored, free); return [=](const char * delim) mutable -> const char * { const char *b = strtok_r(stored, delim, &tmpstorage); stored = nullptr; return b; }; } ``` fails, as shown by AddressSanitizer.

Original source