为何Herb Sutter的monitor示例采用T而非T&或T&&?
Hey there, let's take a close look at that monitor class Herb Sutter walked through in his 2012 C++ and Beyond talk—specifically around the 40:30 mark in the C++ Concurrency session. Here's the exact code he showed:
template<typename T> class monitor { private: mutable T t; mutable std::mutex m; public: monitor(T t_ = T{}) : t{t_} {} template<typename F> auto operator()(F f) const -> decltype(f(t)) { std::lock_guard<std::mutex> _{m}; return f(t); } };
Now, if you've tried using this in real code, you've probably hit a snag: the constructor only accepts a T by value, with no overloads for lvalue references (T&) or rvalue references (T&&). That creates a few headaches:
- Non-copyable types (like
std::unique_ptr) won't work at all here—since passing by value requires a copy constructor, which these types intentionally don't have. - For large objects (like a big
std::vector), passing by value forces an unnecessary copy, which eats up performance for no good reason. - You can't directly move an existing object into the
monitor; you'd have to do awkward workarounds to transfer ownership without copying.
Luckily, fixing this is straightforward by adding the missing constructor overloads and leveraging move semantics properly. Here's the updated, more flexible version:
template<typename T> class monitor { private: mutable T t; mutable std::mutex m; public: // Default constructor for empty initialization monitor() : t{} {} // Original pass-by-value constructor, optimized with move explicit monitor(T t_) : t{std::move(t_)} {} // Overload for lvalue references (copy existing objects efficiently) explicit monitor(const T& t_) : t{t_} {} // Overload for rvalue references (enable move semantics) explicit monitor(T&& t_) : t{std::move(t_)} {} template<typename F> auto operator()(F f) const -> decltype(f(t)) { std::lock_guard<std::mutex> _{m}; return f(t); } };
Let's break down the key improvements:
- We added a default constructor for cases where you don't have an initial value to pass in.
- The original pass-by-value constructor now uses
std::moveto transfer the incomingTinto our member variable—this cuts out an extra copy that would have happened in the original code. - The lvalue reference overload (
const T&) lets us copy an existing object directly into themonitorwithout an unnecessary intermediate copy. - The rvalue reference overload (
T&&) enables perfect forwarding, so we can move objects into themonitorefficiently—this is what makes non-copyable types likestd::unique_ptrwork with the monitor. - We marked all constructors
explicitto prevent accidental implicit conversions, which is a safe practice for wrapper classes like this (it stops code from silently converting aTto amonitor<T>when you don't want it to).
Here's how you can use these new constructors in practice:
// Create an empty monitor with a default-constructed vector monitor<std::vector<int>> empty_monitor; // Copy an existing vector into the monitor std::vector<int> my_vec{1, 2, 3, 4}; monitor<std::vector<int>> copied_monitor(my_vec); // Move a unique_ptr directly into the monitor (no copy possible!) monitor<std::unique_ptr<int>> moved_monitor(std::make_unique<int>(42));
内容的提问来源于stack exchange,提问作者Michael Conlen

