Concurrent Code & Object Lifetimes¶

  • Mutable, conditionally thread safe, objects may only be accessed from one execution context at a time
    • Such objects can be safely moved, or copied, between execution contexts
{
    int x = 42;
    auto r = async([_x = x]() mutable { // copy object to new context
        _x += 5;
        return _x;
    });

    cout << x << endl;
    cout << r.get() << endl;
}
{
    auto p = make_unique<int>(42);
    auto r = async([_p = move(p)]() mutable { // move object to new context
        *_p += 5;
        return move(_p);
    });

    cout << static_cast<bool>(p) << endl;
    cout << *r.get() << endl;
}
  • const objects are assumed to be thread safe, and can safely be shared by more than one context
    • Care must be taken if you have mutable members
{
    auto p = make_shared<const string>("Hello World!");
    auto r = async([_p = p] { return _p; }); // share object between contexts

    cout << *p << endl;
    cout << *r.get() << endl;
}
  • Futures allow us to associate a result with a particular task
    • Sometimes it is useful to have the result still owned by another context
    • std::weak_ptr<> is one way to track the lifetime without taking ownership
{
    struct photoshop {
        shared_ptr<string> _document = make_shared<string>("best.jpg");

        sequential_process _process;
    } ps;

    weak_ptr<string> doc_token =
        async_packaged(ps._process, [&] { return weak_ptr<string>(ps._document); })
            .get();

    // ps._process.async([&]{ ps._document = make_shared<string>("better.png"); });

    ps._process.async([&] {
        if (auto p = doc_token.lock()) *p = "renamed.jpg";
    });

    ps._process.async([&] { cout << *ps._document << endl; });
}
{
    struct photoshop {
        shared_ptr<string> _document = make_shared<string>("best.jpg");

        sequential_process _process;
    } ps;

    weak_ptr<string> doc_token =
        async_packaged(ps._process, [&] { return weak_ptr<string>(ps._document); })
            .get();

    ps._process.async([&] { ps._document = make_shared<string>("better.png"); });

    ps._process.async([&] {
        if (auto p = doc_token.lock()) *p = "renamed.jpg";
    });

    ps._process.async([&] { cout << *ps._document << endl; });
}
  • std::weak_ptr<> also has the advantage that it will not create a retain loop
  • Makes it more clear that the operation doesn't own the object
  • apollo has a track library that can be used to track object lifetimes which are not owned by std::shared_ptr<>
  • apollo::track(T) will return a weak pointer type when T is
    • std::shared_ptr<>
    • A pointer to an object derived from std::enabled_shared_from_this<>
    • An Objective C/C++ __strong pointer
    • A pointer to an object derived from apollo::enable_track<>
  • Weak pointer type are also useful to avoid retain loops with delegates