C++11 Wrap Pattern

Herb Sutter gave a great talk at the C++ and Beyond 2012 conference on concurrency in C++11.

The talk had an interesting interlude on how to wrap all functions performed on an existing type T in order to inject any desired behavior, and that's what this post is all about.
The pattern basically looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <typename T>
class wrap
{
public:
    wrap(T wrapped = T{}) : m_wrapped{wrapped}
    {}
     
    // operator() takes any code that accepts a wrapped T
    // will usually be a lambda
    template <typename F>
    auto operator()(F func) -> decltype(func(m_wrapped))
    {
        // do any pre-function call wrapper work
        auto result = func(m_wrapped);
        // do any post-function call wrapper work
        return result;
    }
     
private:
    T m_wrapped;
    // ... other state required by wrapper
};

This wrap class template can now be used by instantiating it and calling operator() on it with an anonymous lambda function that accepts the wrapped type as single argument.

1
2
3
4
5
6
7
wrap<X> wrapper;
wrapper([](X& x)
{
    x.call_something(foo, bar);
    x.do_something_else();
    std::cout << x.print() << std::endl;
});

A concrete example of using this pattern is a monitor wrapper class, which is similar to the synchronized keyword in Java.
A monitor wraps every function call (or a group of function calls) on an existing class in a mutex lock to make the class thread-safe.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
template <typename T>
class monitor
{
public:
    monitor(T wrapped = T{}) : m_wrapped{wrapped}
    {}
     
    template <typename F>
    auto operator()(F func) -> decltype(func(m_wrapped))
    {
        std::lock_guard<mutex> lock{m_mutex};
        return func(m_wrapped);
    }
 
private:
    mutable T m_wrapped;
    mutable std::mutex m_mutex;
};

Note that m_wrapped is mutable, which is required so that func(m_wrapped) works if func takes the T as a non-const reference T&.

Mutable in C++11 means thread-safe, which means either bitwise const or internally synchronized. Our mutex lock guard guarantees that by the time the m_wrapped is used in func(m_wrapped) it will be internally sychronized, so making m_wrapped mutable is perfectly acceptable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
monitor<string> s = "start\n";
std::vector<std::future<void>> tasks;
 
// start a bunch of tasks
for (int i = 0; i < 5; ++i)
{
    tasks.push_back(async([&,i]
    {
        // single transaction, synchronized modification of s
        s([=](string& s)
        {
            s += "transaction " + to_string(i) + " of 5";
            s += '\n';
        });
         
        // do some more work
         
        // single transaction, synchronized read of s
        s([](string& s)
        {
            std::cout << s;
        });
    });
}
 
// join all tasks
for (auto& task : tasks)
    task.wait();

Note that T used in wrap or monitor could also be a reference, if you don't want to make a copy of T. This is useful for capturing stateful objects, such as std::cout.
So it is perfectly valid to do:

1
2
3
4
5
6
7
monitor<ostream&> sync_cout{std::cout};
 
sync_cout([=](ostream& cout)
{
    cout << "Writing stuff to cout..." << std::endl;
    cout << "in a synchronized way" << std::endl;
});

That's a wrap, folks! 🙂