为什么Herb Sutter的监控示例采用了一个' T'而不是' T&'或者' T&&&#39 ;?

时间:2018-04-19 17:43:12

标签: c++ constructor

C++ and Beyond 2012: Herb Sutter - C++ Concurrency的40:30,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(...);

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

2 个答案:

答案 0 :(得分:4)

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

  • 没有特别的理由假设你想在构建时移动T。 (有时,可能会有 - 但显然不是在这次谈话中)。
  • 你绝对不想采用T& - 这意味着其他人无需进入监控器即可访问受监控的数据,即无需锁定,即无需序列化访问...

答案 1 :(得分:1)

只有Herb Sutter才能真正回答这个问题。

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

  1. 它必须放在幻灯片上。
  2. 包含转发构造函数和in_place构造函数只会使读者感到困惑,并且会降低演示监视器模式的范围。
  3. 如果您需要此代码来支持不可复制和不可移动的类型,那么查看std::optional如何实现对它们的支持可能会有所帮助。

    std::optional有两个构造函数重载。

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

    • 另一个构造函数重载将标记类型std::in_placeforward的所有剩余参数直接带到包装类型的构造函数中。 这用于构造包裹类型,因此永远不需要移动。

    以下是一些示例代码: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.
    }