c++ - 有没有办法在模板中透明地使用移动或复制构造函数?
问题描述
我正在尝试以一种既可以使用仅移动对象也可以使用仅复制对象的方式实现通用线程安全队列。这是我尝试过的(为了简单起见,我删除了所有不相关的代码(锁)):
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();
}
由于函数返回左值引用,因此有几个编译错误pop
:T t = q.front()
无法使用移动语义。 T t = std::move(q.front())
不适用于显式删除的移动构造函数,因为运算符重载将解析为已删除的构造函数。曾经出现在函数中的同类问题,push
但通过完美转发解决。
另一个问题显然是return t
绑定到移动构造函数CopyOnly
,我很难理解为什么会这样。
有没有办法pop
同时处理MoveOnly
和CopyOnly
对象?
附带问题:CopyOnly
定义一个像显式删除的移动构造函数这样的对象是否有意义?在什么情况下这样做会有用?因为如果构造函数被隐式删除,它就会起作用。
解决方案
您可以使用constexpr if并检查 if T
is 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 U
将U
.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
类型,它执行pop
after 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)...);
}
};
推荐阅读
- html - 如何在css中制作边框
- functional-programming - 使用条件查询参数构建 URL
- php - codeigniter 和 MongoDB 中的响应问题
- xslt - xslt:进行否定选择的最简单方法是什么?
- jenkins - Jenkins:根据执行shell脚本条件发送邮件
- spyder - 如何留下程序队列以在 Spyder 3.3.0+ 中运行?
- node.js - 使用节点 js 连接 MySQL 时出错
- c# - 如何在 LINQ 中动态传递列名
- node.js - 在 next.js 中触发客户端重新加载
- javascript - 无法使用 jQuery .submit() 获取 $_POST('input')