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:

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.

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.

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.

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:

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! 🙂

Share and Enjoy !

0Shares
0 0 0