首页 > 解决方案 > 有没有办法在模板中透明地使用移动或复制构造函数?

问题描述

我正在尝试以一种既可以使用仅移动对象也可以使用仅复制对象的方式实现通用线程安全队列。这是我尝试过的(为了简单起见,我删除了所有不相关的代码(锁)):

struct MoveOnly
{
  MoveOnly() = default;
  MoveOnly(const MoveOnly& a) = delete;
  MoveOnly& operator=(const MoveOnly& a) = delete;
  MoveOnly(MoveOnly&& a) = default;
  MoveOnly& operator=(MoveOnly&& a) = default;

  std::vector<int> v;

};

struct CopyOnly
{
  CopyOnly() = default;
  CopyOnly(const CopyOnly &a) = default;
  CopyOnly &operator=(const CopyOnly &a) = default;
  CopyOnly(CopyOnly &&a) = delete;
  CopyOnly &operator=(CopyOnly &&a) = delete;

  std::vector<int> v;
};

template <typename T>
class Queue
{
  std::queue<T> q;

public:
  T pop()
  {
    T t = q.front();
    return t;
  }

  void push(T&& t)
  {
    q.push(std::forward<T>(t));
  }
};

int main()
{
  Queue<MoveOnly> qm;
  qm.push(MoveOnly());
  MoveOnly mo = qm.pop();

  Queue<CopyOnly> qc;
  CopyOnly c;
  qc.push(c);
  CopyOnly&& co = qc.pop();
}

由于函数返回左值引用,因此有几个编译错误popT t = q.front()无法使用移动语义。 T t = std::move(q.front())不适用于显式删除的移动构造函数,因为运算符重载将解析为已删除的构造函数。曾经出现在函数中的同类问题,push但通过完美转发解决。

另一个问题显然是return t绑定到移动构造函数CopyOnly,我很难理解为什么会这样。

有没有办法pop同时处理MoveOnlyCopyOnly对象?


附带问题:CopyOnly定义一个像显式删除的移动构造函数这样的对象是否有意义?在什么情况下这样做会有用?因为如果构造函数被隐式删除,它就会起作用。

标签: c++c++11

解决方案


您可以使用constexpr if并检查 if Tis std::move_constructible_v

我还将创建一个emplace代理:

#include <type_traits>

template<typename T>
class Queue {
    std::queue<T> q;

public:
    decltype(auto) pop() {
        if constexpr(std::is_move_constructible_v<T>) {
            T t = std::move(q.front());
            q.pop();
            return t;
        } else {
            T t = q.front();
            q.pop();
            return t;
        }
    }

    template<class... Args>
    decltype(auto) emplace(Args&&... args) {
        return q.emplace(std::forward<Args>(args)...);
    }
};

这是一个 C++11 版本(我之前没有注意到 C++11 标签)。删除移动构造函数和移动赋值运算符CopyOnly真的把它弄得一团糟。您可能永远不应该在实际代码中这样做。

要开始CopyOnly co = qc.pop();工作,pop()需要返回 a const T,否则移动构造函数将成为重载决议的一部分,即使它被删除,它仍然会是,但编译会因为它被删除而失败。

如果对您来说没问题,CopyOnly&& co = qc.pop();您可以const UU.enable_if

template<typename T>
class Queue {
    std::queue<T> q{};

public:
    template<typename U = T>
    typename std::enable_if<std::is_move_constructible<U>::value, U>::type
    pop() {
        U t = std::move(q.front());
        q.pop();
        return t;
    }

    template<typename U = T>
    typename std::enable_if<!std::is_move_constructible<U>::value, const U>::type
    pop() {
        U t = q.front();
        q.pop();
        return t;
    }

    template<class... Args>
    void emplace(Args&&... args) {
        q.emplace(std::forward<Args>(args)...);
    }
};

这是另一个基于 rafix07 的想法构建的 C++11 版本,它具有一个额外的popper类型,它执行popafter return 以处理 gcc 7.3 中可能存在的错误。

template<typename T>
class Queue {
    std::queue<T> q{};

    struct popper {
        std::queue<T>& q_ref;
        ~popper() {
            q_ref.pop();
        }
    };
public:
    using Type = typename
        std::conditional<std::is_move_constructible<T>::value, T, T&>::type;

    T pop() {
        popper pop_after_return{q};
        return std::forward<Type>(q.front());
    }

    template<class... Args>
    void emplace(Args&&... args) {
        q.emplace(std::forward<Args>(args)...);
    }
};

推荐阅读