diff --git a/concurrentqueue.h b/concurrentqueue.h index 99caefc0..421fc630 100644 --- a/concurrentqueue.h +++ b/concurrentqueue.h @@ -1009,6 +1009,18 @@ class ConcurrentQueue else return inner_enqueue(std::move(item)); } + // Enqueues a single item (by constructing it in-place from arguments). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + template + inline bool enqueue_emplace(Args&&... args) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(std::forward(args)...); + } + // Enqueues a single item (by copying it) using an explicit producer token. // Allocates memory if required. Only fails if memory allocation fails (or // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). @@ -1027,6 +1039,17 @@ class ConcurrentQueue return inner_enqueue(token, std::move(item)); } + // Enqueues a single item (by constructing it in-place from arguments) using an explicit + // producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + template + inline bool enqueue_token_emplace(producer_token_t const& token, Args&&... args) + { + return inner_enqueue(token, std::forward(args)...); + } + // Enqueues several items. // Allocates memory if required. Only fails if memory allocation fails (or // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE @@ -1074,6 +1097,18 @@ class ConcurrentQueue else return inner_enqueue(std::move(item)); } + // Enqueues a single item (by constructing it in-place from arguments). + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Thread-safe. + template + inline bool try_enqueue_emplace(Args&&... args) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(std::forward(args)...); + } + // Enqueues a single item (by copying it) using an explicit producer token. // Does not allocate memory. Fails if not enough room to enqueue. // Thread-safe. @@ -1090,6 +1125,16 @@ class ConcurrentQueue return inner_enqueue(token, std::move(item)); } + // Enqueues a single item (by constructing it in-place from arguments) using an explicit + // producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + template + inline bool try_enqueue_token_emplace(producer_token_t const& token, Args&&... args) + { + return inner_enqueue(token, std::forward(args)...); + } + // Enqueues several items. // Does not allocate memory (except for one-time implicit producer). // Fails if not enough room to enqueue (or implicit production is @@ -1363,17 +1408,17 @@ class ConcurrentQueue // Queue methods /////////////////////////////// - template - inline bool inner_enqueue(producer_token_t const& token, U&& element) + template + inline bool inner_enqueue(producer_token_t const& token, Args&&... args) { - return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue(std::forward(element)); + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue(std::forward(args)...); } - template - inline bool inner_enqueue(U&& element) + template + inline bool inner_enqueue(Args&&... args) { auto producer = get_or_add_implicit_producer(); - return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue(std::forward(element)); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue(std::forward(args)...); } template @@ -1845,8 +1890,8 @@ class ConcurrentQueue } } - template - inline bool enqueue(U&& element) + template + inline bool enqueue(Args&&... args) { index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); index_t newTailIndex = 1 + currentTailIndex; @@ -1912,11 +1957,11 @@ class ConcurrentQueue ++pr_blockIndexSlotsUsed; } - MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(args)...))) { // The constructor may throw. We want the element not to appear in the queue in // that case (without corrupting the queue): MOODYCAMEL_TRY { - new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(args)...); } MOODYCAMEL_CATCH (...) { // Revert change to the current block, but leave the new block available @@ -1938,14 +1983,14 @@ class ConcurrentQueue blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release); pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); - MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(args)...))) { this->tailIndex.store(newTailIndex, std::memory_order_release); return true; } } // Enqueue - new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(args)...); this->tailIndex.store(newTailIndex, std::memory_order_release); return true; @@ -2483,8 +2528,8 @@ class ConcurrentQueue } } - template - inline bool enqueue(U&& element) + template + inline bool enqueue(Args&&... args) { index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); index_t newTailIndex = 1 + currentTailIndex; @@ -2516,10 +2561,10 @@ class ConcurrentQueue #endif newBlock->ConcurrentQueue::Block::template reset_empty(); - MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(args)...))) { // May throw, try to insert now before we publish the fact that we have this new block MOODYCAMEL_TRY { - new ((*newBlock)[currentTailIndex]) T(std::forward(element)); + new ((*newBlock)[currentTailIndex]) T(std::forward(args)...); } MOODYCAMEL_CATCH (...) { rewind_block_index_tail(); @@ -2534,14 +2579,14 @@ class ConcurrentQueue this->tailBlock = newBlock; - MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(args)...))) { this->tailIndex.store(newTailIndex, std::memory_order_release); return true; } } // Enqueue - new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(args)...); this->tailIndex.store(newTailIndex, std::memory_order_release); return true; diff --git a/tests/unittests/unittests.cpp b/tests/unittests/unittests.cpp index b4c4d9cc..9d6f73e3 100644 --- a/tests/unittests/unittests.cpp +++ b/tests/unittests/unittests.cpp @@ -199,6 +199,29 @@ struct Moveable { #endif }; +struct Emplaceable { + Emplaceable() : moved(false), copied(false), copyData(0), moveData(0) {} + Emplaceable(const Copyable& copyData, Moveable&& moveData) : moved(false), copied(false), copyData(copyData), moveData(std::move(moveData)) { } + Emplaceable(Emplaceable&& o) MOODYCAMEL_NOEXCEPT : moved(true), copied(o.copied), copyData(o.copyData), moveData(std::move(o.moveData)) { } + void operator=(Emplaceable&& o) MOODYCAMEL_NOEXCEPT { moved = true; copied = o.copied; copyData = o.copyData; moveData = std::move(o.moveData); } + bool moved; + bool copied; + Copyable copyData; + Moveable moveData; + +#if defined(_MSC_VER) && _MSC_VER < 1800 + // VS2012's std::is_nothrow_[move_]constructible is broken, so the queue never attempts to + // move objects with that compiler. In this case, we don't know whether it's really a copy + // or not being done, so give the benefit of the doubt (given the tests pass on other platforms) + // and assume it would have done a move if it could have (don't set copied to true). + Emplaceable(Emplaceable const& o) MOODYCAMEL_NOEXCEPT : moved(o.moved), copied(o.copied), copyData(o.copyData), moveData(o.moveData) { } + void operator=(Emplaceable const& o) MOODYCAMEL_NOEXCEPT { moved = o.moved; copied = o.copied; copyData = o.copyData; moveData = o.moveData; } +#else + Emplaceable(Emplaceable const& o) MOODYCAMEL_NOEXCEPT : moved(o.moved), copied(true), copyData(o.copyData), moveData(o.moveData) { } + void operator=(Emplaceable const& o) MOODYCAMEL_NOEXCEPT { moved = o.moved; copied = true; copyData = o.copyData; moveData = o.moveData; } +#endif +}; + struct ThrowingMovable { static std::atomic& ctorCount() { static std::atomic c; return c; } static std::atomic& destroyCount() { static std::atomic c; return c; } @@ -3310,6 +3333,20 @@ class ConcurrentQueueTests : public TestClass ASSERT_OR_FAIL(!item.copied); ASSERT_OR_FAIL(!q.try_dequeue(item)); } + // enqueue_emplace(Args&&...) + { + ConcurrentQueue q; + ASSERT_OR_FAIL(q.enqueue_emplace(Copyable(1234), Moveable(12345))); + Emplaceable item; + ASSERT_OR_FAIL(q.try_dequeue(item)); + ASSERT_OR_FAIL(item.copyData.id == 1234); + ASSERT_OR_FAIL(item.moveData.id == 12345); + ASSERT_OR_FAIL(item.moved); + ASSERT_OR_FAIL(!item.copied); + ASSERT_OR_FAIL(item.moveData.moved); + ASSERT_OR_FAIL(!item.moveData.copied); + ASSERT_OR_FAIL(!q.try_dequeue(item)); + } { ConcurrentQueue q; Moveable original(12345); @@ -3356,6 +3393,21 @@ class ConcurrentQueueTests : public TestClass ASSERT_OR_FAIL(!item.copied); ASSERT_OR_FAIL(!q.try_dequeue(item)); } + // enqueue_emplace(Token, Args&&...) + { + ConcurrentQueue q; + ProducerToken t(q); + ASSERT_OR_FAIL(q.enqueue_token_emplace(t, Copyable(1234), Moveable(12345))); + Emplaceable item; + ASSERT_OR_FAIL(q.try_dequeue(item)); + ASSERT_OR_FAIL(item.copyData.id == 1234); + ASSERT_OR_FAIL(item.moveData.id == 12345); + ASSERT_OR_FAIL(item.moved); + ASSERT_OR_FAIL(!item.copied); + ASSERT_OR_FAIL(item.moveData.moved); + ASSERT_OR_FAIL(!item.moveData.copied); + ASSERT_OR_FAIL(!q.try_dequeue(item)); + } { ConcurrentQueue q; ProducerToken t(q); @@ -3423,6 +3475,21 @@ class ConcurrentQueueTests : public TestClass ASSERT_OR_FAIL(!q.try_dequeue(item)); } + // try_enqueue_emplace(Args&&...) + { + ConcurrentQueue q; + ASSERT_OR_FAIL(q.try_enqueue_emplace(Copyable(1234), Moveable(12345))); + Emplaceable item; + ASSERT_OR_FAIL(q.try_dequeue(item)); + ASSERT_OR_FAIL(item.copyData.id == 1234); + ASSERT_OR_FAIL(item.moveData.id == 12345); + ASSERT_OR_FAIL(item.moved); + ASSERT_OR_FAIL(!item.copied); + ASSERT_OR_FAIL(item.moveData.moved); + ASSERT_OR_FAIL(!item.moveData.copied); + ASSERT_OR_FAIL(!q.try_dequeue(item)); + } + // try_enqueue(Token, T const&) { ConcurrentQueue q; @@ -3471,6 +3538,22 @@ class ConcurrentQueueTests : public TestClass ASSERT_OR_FAIL(!q.try_dequeue(item)); } + // try_enqueue_emplace(Token, Args&&...) + { + ConcurrentQueue q; + ProducerToken t(q); + ASSERT_OR_FAIL(q.try_enqueue_token_emplace(t, Copyable(1234), Moveable(12345))); + Emplaceable item; + ASSERT_OR_FAIL(q.try_dequeue(item)); + ASSERT_OR_FAIL(item.copyData.id == 1234); + ASSERT_OR_FAIL(item.moveData.id == 12345); + ASSERT_OR_FAIL(item.moved); + ASSERT_OR_FAIL(!item.copied); + ASSERT_OR_FAIL(item.moveData.moved); + ASSERT_OR_FAIL(!item.moveData.copied); + ASSERT_OR_FAIL(!q.try_dequeue(item)); + } + // enqueue_bulk(It itemFirst, size_t count) { ConcurrentQueue q;