Live Hacking: Handwritten Functors, And Capturing By Reference (Pitfalls)

Original, Handwritten Functor (By Copy)

#include <functional>
#include <string>
#include <iostream>

class print_message_func
{
public:
    print_message_func(const std::string& message) : _message(message) {}
    void operator()()
    {
        std::cout << _message << std::endl;
    }
private:
    std::string _message;
};


static std::function<void()> create_print_function(const std::string& message)
{
    auto print_message = print_message_func(message);
    return print_message;
}

int main()
{
    auto p = create_print_function("howdy");
    p();
    return 0;
}
$ code/c++11-lambda-capture-handwritten
howdy

Possible Pitfall: Capturing A Reference

#include <functional>
#include <string>
#include <iostream>

class print_message_func
{
public:
    print_message_func(const std::string& message) : _message(message) {}
    void operator()()
    {
        std::cout << _message << std::endl;  // using reference! STILL VALID?
    }
private:
    const std::string& _message;
};


static std::function<void()> create_print_function(const std::string& message)
{
    auto print_message = print_message_func(message);
    return print_message;
}

int main()
{
    auto p = create_print_function("howdy");  // <--- temporary std::string object; LIFETIME?
    p();
    return 0;
}
$ code/c++11-lambda-capture-handwritten-reference
howdy
  • Program creates a temporary std::string object ("howdy")

  • Functor stores a reference to it, and uses it later

  • Attentive programmer jumps up and shouts!

Attention

Nothing happens though (pity!)

⟶ lets dissect this for a moment …

Lifetime Of Temporary, Made Explicit

  • Replace std::string with custom class

    • Debug output in ctor and dtor

    • Stores only pointer to "howdy"

#include <functional>
#include <string>
#include <iostream>

struct String
{
    String(const char* s) : s(s) { std::cout << "ctor" << std::endl; }
    ~String() { std::cout << "dtor" << std::endl; }
    const char* s;
};

class print_message_func
{
public:
    print_message_func(const String& message) : _message(message) {}
    void operator()()
    {
        std::cout << _message.s << std::endl;  // using reference! STILL VALID?
    }
private:
    const String& _message;
};


static std::function<void()> create_print_function(const String& message)
{
    auto print_message = print_message_func(message);
    return print_message;
}

int main()
{
    auto p = create_print_function("howdy");  // <--- temporary std::string object; LIFETIME?
    p();
    return 0;
}
$ code/c++11-lambda-capture-handwritten-reference-gosh
ctor
dtor
howdy

Danger

Obviously String instance destroyed before use!

Lifetime Of Temporary, Made Explicit ⟶ 🚑

#include <functional>
#include <string>
#include <cstring>
#include <iostream>

struct String
{
    String(const char* s)
    : s(new char[strlen(s)+1])
    {
        strcpy(this->s, s);
        std::cout << "ctor" << std::endl;
    }
    ~String()
    { 
        std::cout << "dtor" << std::endl;        
        delete[] s;
    }
    char* s;
};

class print_message_func
{
public:
    print_message_func(const String& message) : _message(message) {}
    void operator()()
    {
        std::cout << _message.s << std::endl;  // using reference! STILL VALID?
    }
private:
    const String& _message;
};


static std::function<void()> create_print_function(const String& message)
{
    auto print_message = print_message_func(message);
    return print_message;
}

int main()
{
    auto p = create_print_function("howdy");  // <--- temporary std::string object; LIFETIME?
    p();
    return 0;
}
$ code/c++11-lambda-capture-handwritten-reference-gosh-boom
ctor
dtor

$ valgrind code/c++11-lambda-capture-handwritten-reference-gosh-boom
... hell ...