【问题标题】:Thread safe wrapper around std::queue doesn't build if storing unique_ptr, but std::queue works如果存储 unique_ptr,则不会构建围绕 std::queue 的线程安全包装器,但 std::queue 有效
【发布时间】:2019-12-24 07:23:33
【问题描述】:

我有一个假定的线程安全阻塞队列实现,它应该是 std::queue 的包装器 下面是它的实现:

template <typename _Tp>
class SharedQueue
{
  public:
    explicit SharedQueue(bool isBlocking = true) : isBlocking_(isBlocking) {}

    virtual ~SharedQueue() {}

    virtual const _Tp& front()
    {
        std::unique_lock<std::mutex> mlock(mtx_);

        // if this is a blocking queue, wait to be notified when when a new object is added
        if (isBlocking_)
        {
            while (queue_.empty())
            {
                cv_.wait(mlock);
            }
        }

        return queue_.front();
    }

    virtual bool empty() const
    {
        std::unique_lock<std::mutex> mlock(mtx_);

        return queue_.empty();
    }

    virtual size_t size() const
    {
        std::unique_lock<std::mutex> mlock(mtx_);

        return queue_.size();
    }

    virtual void push(const _Tp& value)
    {
        std::unique_lock<std::mutex> mlock(mtx_);

        queue_.push(value);

        if (isBlocking_)
        {
            if (queue_.size() == 1)
            {
                cv_.notify_all();
            }
        }
    }

    virtual void push(_Tp&& value)
    {
        {
            std::unique_lock<std::mutex> mlock(mtx_);

            queue_.push(std::move(value));

            if (isBlocking_)
            {
                if (queue_.size() == 1)
                {
                    cv_.notify_all();
                }
            }
        }
    }

    template <typename... _Args>
    void emplace(_Args&&... __args)
    {
        {
            std::unique_lock<std::mutex> mlock(mtx_);

            queue_.emplace(std::forward<_Args>(__args)...);

            if (isBlocking_)
            {
                if (queue_.size() == 1)
                {
                    cv_.notify_all();
                }
            }
        }
    }

    virtual void pop()
    {
        std::unique_lock<std::mutex> mlock(mtx_);

        if (!queue_.empty())
        {
            queue_.pop();
        }
    }

  private:
    bool isBlocking_;

    mutable std::mutex mtx_;

    mutable std::condition_variable cv_;

    std::queue<_Tp> queue_;
};

我希望能够将 unique_ptr 放置在此队列中,并且我知道当从客户端应用程序将 unique_ptr 推入队列时,我必须在 unique_ptr 上调用 std::move。 这是我的问题... 在我的 main 中,当我按如下方式创建 std::queue 时,我的程序编译得很好

std::queue<std::unique_ptr<int32_t>> q1;

但是,当我创建如下 SharedQueue 的实例时,程序无法编译。

SharedQueue<std::unique_ptr<int32_t>> q2;

我收到一条错误消息,告诉我复制构造函数在 unique_ptr 类中被删除,这是可以理解的。我想我只是想知道 std::queue 做了什么代码可以编译,而我的代码“似乎”是 std::queue 的包装器并类似地实现操作。

编辑: 如果我替换,我可以让 SharedQueue 编译

queue_.push(value);

queue_.push(std::forward<_Tp>(const_cast<_Tp&>(value)));

在接受左值引用的 push 方法中。

【问题讨论】:

  • 您是否尝试将任何值推送到q1
  • @Shawn 是的,当我使用 std::move 正确执行此操作时,我不会收到编译错误,但是当我尝试在没有它的情况下调用 push 时,我收到一个错误,告诉我的复制 ctor在 std::unique_ptr 类中被删除
  • 没错。在不使用std::move() 的情况下查看您的SharedQueue 推入队列的位置?
  • 那不要和不可复制的类型一起使用?
  • @tree 因为std::queue 使用模板/SFINAE 魔法和std::enable_if 来包含仅在类可复制时才复制的函数。

标签: c++ multithreading c++11


【解决方案1】:

您的问题在于:

    virtual void push(const _Tp& value)
    {
        std::unique_lock<std::mutex> mlock(mtx_);
        queue_.push(value);
     [...]

如果您删除 virtual 关键字,它会正常编译,但会退回到 std::queue 的情况:如果您使用该函数,它只会发出错误。

我假设使用virtual 会发出错误,因为它需要编译器生成 vtable,因此需要生成并指向函数代码。

一种使其保持虚拟并仍然有效的方法可能是使用 SFINAE 以及从您的基类继承的类的 std::is_copy_constructiblestd::is_move_constructible 特化,其中 push 成员函数是纯虚拟的。


Here 是一个(不好。不要那样做 OO C++。真的)该想法的示例实现。请注意,与您不能只“不调用代码”虚拟push 的方式相同,您不能只删除对特化的推送,而必须在运行时执行某些操作。在这个例子中,抛出了一个异常。

有更好的方法来做到这一点。我在考虑你真的需要那些成员函数是虚拟的。否则,只需删除关键字即可解决您的问题。

一种方法是对您的 SharedQueue 类进行完全专业化,将 push 重载放在您需要的地方,但以组合方式重用其余代码,而不是通过继承。

另一个人会在 queue 包装器上做同样的事情,它会更小,更易读,然后在您当前的代码中使用它(Example,同样,不好,但说明了这一点)。

优雅的,C++17 或更新的方式,就是使用if constexpr

virtual void push(const _Tp& value) {
    std::unique_lock<std::mutex> mlock(mtx_);

    if constexpr(std::is_copy_constructible_v<_Tp>){
      queue_.push(value);
    } else{
      throw std::invalid_argument("The type _Tp can't be copy constructed");
    }

    if (isBlocking_) {
      if (queue_.size() == 1) {
        cv_.notify_all();
      }
    }
  }

【讨论】:

    猜你喜欢
    • 2023-03-23
    • 2022-01-20
    • 1970-01-01
    • 2016-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-26
    相关资源
    最近更新 更多