输出参数和移动语义
Out-parameters and move semantics
考虑一个无锁并发数据结构的情况,其中 pop()
操作需要 return 一个项目或 false
如果 cointainer 为空(而不是阻塞或抛出).数据结构以用户类型 T
为模板,它可能很大(但也可能是轻量级的,我希望在任何一种情况下都高效)。 T
至少必须是可移动的,但我不希望它必须是可复制的。
我认为函数签名应该是 bool DS<T>::pop(T &item)
,因此该项目被提取为输出参数而不是 return 值(它用于指示成功或失败)。但是,我实际上如何传递它呢?假设有一个底层缓冲区。我会做 item = std::move(_buff[_tail])
——进入参考输出参数有意义吗?一个缺点是用户必须传入一个默认构造的 T,这有点违背有效的 RAII,因为如果函数失败,结果是一个尚未实际初始化其资源的对象。
另一种选择是 returning std::pair<bool, T>
而不是使用输出参数,但同样需要一个默认可构造的 T
,在这种情况下不包含任何资源失败,对于 return std::make_pair(false, T)
.
第三个选项是 return 将项目作为 std::unique_ptr<T>
,但在 T
是指针或其他轻量类型的情况下,这会产生无用的开销。虽然我可以只将指针存储在数据结构中,实际项目存储在外部,但这不仅会导致额外的取消引用和缓存未命中的惩罚,还会消除直接存储在缓冲区中的项目添加的自然填充,并有助于最小化生产者和消费者线程命中相同的缓存行。
#include <boost/optional.hpp>
#include <string>
template<class T>
struct atomic_queue
{
using value_type = T;
auto pop() -> boost::optional<T>
{
boost::optional<T> result;
/*
* insert atomic ops here, optionally filling result
*/
return result;
};
auto push(T&& arg) -> bool
{
/*
* insert atomic ops here, optionally stealing arg
*/
return true;
};
static auto make_empty_result() {
return boost::optional<T>();
}
};
struct difficult {
difficult(std::string);
difficult() = delete;
difficult(difficult const&) = delete;
difficult& operator=(difficult const&) = delete;
difficult(difficult &&) = default;
difficult& operator=(difficult &&) = default;
};
extern void spin();
int main()
{
atomic_queue<difficult> q;
auto d = difficult("arg");
while(not q.push(std::move(d)))
spin();
auto popped = q.make_empty_result();
while(not (popped = q.pop()))
spin();
auto& val = popped.get();
}
考虑一个无锁并发数据结构的情况,其中 pop()
操作需要 return 一个项目或 false
如果 cointainer 为空(而不是阻塞或抛出).数据结构以用户类型 T
为模板,它可能很大(但也可能是轻量级的,我希望在任何一种情况下都高效)。 T
至少必须是可移动的,但我不希望它必须是可复制的。
我认为函数签名应该是 bool DS<T>::pop(T &item)
,因此该项目被提取为输出参数而不是 return 值(它用于指示成功或失败)。但是,我实际上如何传递它呢?假设有一个底层缓冲区。我会做 item = std::move(_buff[_tail])
——进入参考输出参数有意义吗?一个缺点是用户必须传入一个默认构造的 T,这有点违背有效的 RAII,因为如果函数失败,结果是一个尚未实际初始化其资源的对象。
另一种选择是 returning std::pair<bool, T>
而不是使用输出参数,但同样需要一个默认可构造的 T
,在这种情况下不包含任何资源失败,对于 return std::make_pair(false, T)
.
第三个选项是 return 将项目作为 std::unique_ptr<T>
,但在 T
是指针或其他轻量类型的情况下,这会产生无用的开销。虽然我可以只将指针存储在数据结构中,实际项目存储在外部,但这不仅会导致额外的取消引用和缓存未命中的惩罚,还会消除直接存储在缓冲区中的项目添加的自然填充,并有助于最小化生产者和消费者线程命中相同的缓存行。
#include <boost/optional.hpp>
#include <string>
template<class T>
struct atomic_queue
{
using value_type = T;
auto pop() -> boost::optional<T>
{
boost::optional<T> result;
/*
* insert atomic ops here, optionally filling result
*/
return result;
};
auto push(T&& arg) -> bool
{
/*
* insert atomic ops here, optionally stealing arg
*/
return true;
};
static auto make_empty_result() {
return boost::optional<T>();
}
};
struct difficult {
difficult(std::string);
difficult() = delete;
difficult(difficult const&) = delete;
difficult& operator=(difficult const&) = delete;
difficult(difficult &&) = default;
difficult& operator=(difficult &&) = default;
};
extern void spin();
int main()
{
atomic_queue<difficult> q;
auto d = difficult("arg");
while(not q.push(std::move(d)))
spin();
auto popped = q.make_empty_result();
while(not (popped = q.pop()))
spin();
auto& val = popped.get();
}