Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I capture a smart pointer in a lambda?

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.

like image 453
Demi Avatar asked Dec 19 '13 07:12

Demi


2 Answers

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));
}
like image 91
Angew is no longer proud of SO Avatar answered Oct 19 '22 14:10

Angew is no longer proud of SO


Just capture the variable by value and let the copy constructor and destructor worry about ownership semantics- that's what smart pointers are for.

like image 22
mrinal Avatar answered Oct 19 '22 13:10

mrinal