vendredi 28 juillet 2017

Out-parameters and move semantics

Consider a case of a lock-free concurrent data structure where a pop() operation needs to return an item or false if the cointainer is empty (rather than blocking or throwing). The data structure is templated on a user type T, which can potentially be large (but also could be lightweight, and I want things to be efficient in either case). T has to be at least movable, but I don't want it to have to be copyable.

I was thinking that the function signature would be bool DS<T>::pop(T &item) so the item is extracted as an out-parameter rather than return value (which instead is used to indicate success or failure). However, how do I actually pass it out? Assume there's an underlying buffer. Would I do item = std::move(_buff[_tail])—does it make sense to move into a reference out-parameter? A downside is that the user would have to pass in a default-constructed T, which goes a bit against effective RAII because the result is an object that hasn't actually initialized its resources if the function fails.

Another option is returning an std::pair<bool, T> rather than using an out-parameter, but there again needs to be a default-constructible T which holds no resource in the case of failure, for the return std::make_pair(false, T).

A third option would be returning the item as std::unique_ptr<T>, but this incurs useless overhead in the case of T being a pointer or another lightweight type. While I could store just pointers in the data structure with the actual items stored externally, that incurs not just the penalty of additional dereferences and cache misses, but also removes the natural padding that items stored directly in the buffer add and help minimize producer and consumer threads from hitting the same cache lines.

Aucun commentaire:

Enregistrer un commentaire