为什么赫伯·萨特(Herb Sutter)的监视器示例采用"T"而不是"T&"或"T&&"?

Why does Herb Sutter's monitor example take a 'T' rather than 'T&' or 'T&&'?

本文关键字:萨特 Herb 监视器 Sutter 为什么      更新时间:2023-10-16

在 40:30 在 C++ 及以后 2012: Herb Sutter - C++ 并发,Herb Sutter 根据他的包装器习语展示了一个监视器类:

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<mutex> _{m}; return f(t);
}
}

请注意构造函数接受T而不是T&T&&,并且不包括构造函数。我想象的用例是:

monitor<Foo> foo = Foo(...);

由于缺少移动构造函数而失败。

Sutter 示例中的构造函数采用T而不是T &T&&,因为值语义是C++中的默认值,并且:

  • 没有特别的理由假设您希望在构造时移动T。(有时,可能会有 - 但显然不是在这个演讲中)。
  • 您绝对不想采取T&- 这意味着其他人将访问您受监控的数据而不去监视器,即不锁定,即不序列化访问......

只有赫伯·萨特才能真正回答这个问题。

如果我不得不猜测,我会说他的理由是:

  1. 它必须适合幻灯片。
  2. 包括转发构造函数和in_place构造函数只会使读者感到困惑,并减损演示监视模式的重点。

如果需要此代码来支持不可复制和不可移动的类型,请查看std::optional如何实现对它们的支持可能会有所帮助。

也就是说,std::optional有两个构造函数重载。

  • 一个构造函数重载采用转发引用(即U&&) 并使用std::forward来构造包装类型。这增加了对不可复制类型的支持。

  • 另一个构造函数重载采用标记类型,std::in_place,并将所有剩余的参数直接forward到包装类型的构造函数。 这用于就地构造包装类型,因此永远不需要移动它。

下面是一些示例代码: https://godbolt.org/g/hWmcTA

#include <utility>
#include <mutex>
template<typename T>
class monitor {
private:
mutable T t;
mutable std::mutex m;
public:
monitor()
: t{}
{ }
template<typename Y>
monitor(Y&& y)
: t{std::forward<Y>(y)}
{ }
template<typename... Args>
monitor(std::in_place_t, Args&&... args)
: t{std::forward<Args>(args)...}
{ }
template<typename F>
auto operator()(F f) const -> decltype(f(t))
{
std::lock_guard<std::mutex> _{m}; return f(t);
}
};
// A non-movable type, just for testing.
struct NonMovable {
NonMovable(int n = 0, double d = 0)
: n_{n}, d_{d}
{ }
NonMovable(const NonMovable&) = delete;
NonMovable(NonMovable&&) = delete;
NonMovable& operator=(const NonMovable&) = delete;
NonMovable& operator=(NonMovable&&) = delete;
private:
int n_;
double d_;
};
int main() {
// Non-movable type.
monitor<NonMovable> m4; //< Good. Uses default constructor.
//monitor<NonMovable> m5{NonMovable{1, 2.2}};//< Bad! Forwarding constructor.
monitor<NonMovable> m6{std::in_place, 1, 2.2};//< Good. In-place constructor.
// And a movable type, just to make sure we didn't break anything.
monitor<int> m1; //< Good. Uses default constructor.
monitor<int> m2{1}; //< Good. Forwarding constructor.
monitor<int> m3{std::in_place, 1}; //< Good. In-place constructor.
}