输出参数和移动语义

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();
}