Skip to content

Commit 4d797f1

Browse files
Move values (#71)
* Move values * Set release version * Use size of internal queue * Document known issues * Test with more std algorithms * Test writing on closed channel * Improve documentation
1 parent 8d0616c commit 4d797f1

8 files changed

Lines changed: 176 additions & 59 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cmake_minimum_required(VERSION 3.12)
22
project(cpp_channel)
3-
set(PROJECT_VERSION 1.2.0)
3+
set(PROJECT_VERSION 1.2.1)
44

55
set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard")
66
set(CMAKE_CXX_STANDARD_REQUIRED YES)

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Channel
22

33
[![build](https://github.com/andreiavrammsd/cpp-channel/actions/workflows/cmake.yml/badge.svg)](https://github.com/andreiavrammsd/cpp-channel/actions) [![codecov](https://codecov.io/github/andreiavrammsd/cpp-channel/graph/badge.svg?token=CKQ0TVW62Z)](https://codecov.io/github/andreiavrammsd/cpp-channel)
4-
[![documentation](https://github.com/andreiavrammsd/cpp-channel/workflows/doc/badge.svg)](https://andreiavrammsd.github.io/cpp-channel/)
4+
[![documentation](https://github.com/andreiavrammsd/cpp-channel/actions/workflows/doc.yml/badge.svg)](https://andreiavrammsd.github.io/cpp-channel/)
55

66
### Thread-safe container for sharing data between threads (synchronized queue). Header-only. Compatible with C++11.
77

@@ -11,9 +11,10 @@
1111
* Blocking (forever waiting to fetch).
1212
* Range-based for loop supported.
1313
* Close to prevent pushing and stop waiting to fetch.
14-
* Integrates well with STL algorithms in some cases. Eg:
14+
* Integrates with some of the STD algorithms. Eg:
1515
* `std::move(ch.begin(), ch.end(), ...)`
1616
* `std::transform(input_chan.begin(), input_chan.end(), msd::back_inserter(output_chan))`.
17+
* `std::copy_if(chan.begin(), chan.end(), ...);`
1718
* Tested with GCC, Clang, and MSVC.
1819
* Includes stack-based, exception-free alternative (static channel).
1920

@@ -136,6 +137,10 @@ int main() {
136137

137138
See [examples](https://github.com/andreiavrammsd/cpp-channel/tree/master/examples) and [documentation](https://andreiavrammsd.github.io/cpp-channel/).
138139

140+
## Known limitations
141+
142+
* In some cases, the integration with some STD algorithms does not compile with MSVC. See the [Transform test](https://github.com/andreiavrammsd/cpp-channel/blob/master/tests/channel_test.cpp).
143+
139144
<br>
140145

141146
Developed with [CLion](https://www.jetbrains.com/?from=serializer) and [Visual Studio Code](https://code.visualstudio.com/).

examples/multithreading.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <cstdint>
22
#include <cstdlib>
33
#include <iostream>
4+
#include <sstream>
45
#include <thread>
56
#include <utility>
67

@@ -15,7 +16,9 @@ int main()
1516
// Read
1617
const auto out = [](msd::channel<std::int64_t>& ch, std::size_t i) {
1718
for (auto number : ch) {
18-
std::cout << number << " from thread: " << i << '\n';
19+
std::stringstream stream;
20+
stream << number << " from thread: " << i << '\n';
21+
std::cout << stream.str();
1922
std::this_thread::sleep_for(std::chrono::milliseconds(500));
2023
}
2124
};

include/msd/blocking_iterator.hpp

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
namespace msd {
1010

1111
/**
12-
* @brief An iterator that block the current thread, waiting to fetch elements from the channel.
12+
* @brief An iterator that blocks the current thread, waiting to fetch elements from the channel.
1313
*
14-
* Used to implement channel range-based for loop.
14+
* @details Used to implement channel range-based for loop.
1515
*
1616
* @tparam Channel Type of channel being iterated.
1717
*/
@@ -61,7 +61,7 @@ class blocking_iterator {
6161
*
6262
* @return The iterator itself.
6363
*/
64-
blocking_iterator<Channel> operator++() noexcept
64+
blocking_iterator<Channel>& operator++() noexcept
6565
{
6666
if (!chan_->read(value_)) {
6767
is_end_ = true;
@@ -80,7 +80,6 @@ class blocking_iterator {
8080
* @brief Makes iteration continue until the channel is closed and empty.
8181
*
8282
* @param other Another blocking_iterator to compare with.
83-
*
8483
* @return true if the channel is not closed or not empty (continue iterating).
8584
* @return false if the channel is closed and empty (stop iterating).
8685
*/
@@ -95,7 +94,7 @@ class blocking_iterator {
9594
/**
9695
* @brief An output iterator pushes elements into a channel. Blocking until the channel is not full.
9796
*
98-
* Used to integrate with standard algorithms that require an output iterator.
97+
* @details Used to integrate with standard algorithms that require an output iterator.
9998
*
10099
* @tparam Channel Type of channel being iterated.
101100
*/
@@ -137,20 +136,25 @@ class blocking_writer_iterator {
137136
/**
138137
* @brief Writes an element into the channel, blocking until space is available.
139138
*
140-
* @param val The value to be written into the channel.
141-
*
139+
* @param value The value to be written into the channel.
142140
* @return The iterator itself.
141+
* @note There is no effect if the channel is closed.
143142
*/
144-
blocking_writer_iterator& operator=(const value_type& val)
143+
blocking_writer_iterator& operator=(reference value)
145144
{
146-
chan_->write(val);
145+
chan_->write(value);
147146
return *this;
148147
}
149148

150149
/**
151150
* @brief Not applicable (handled by operator=).
152151
*
153152
* @return The iterator itself.
153+
*
154+
* @note It's uncommon to return a reference to an iterator, but I don't want to return a value from the channel.
155+
* This iterator is supposed to be used only to write values.
156+
* I don't know if it's a terrible idea or not, but it looks related to the issue with MSVC
157+
* in the Transform test in tests/channel_test.cpp.
154158
*/
155159
blocking_writer_iterator& operator*() { return *this; }
156160

@@ -176,9 +180,7 @@ class blocking_writer_iterator {
176180
* @brief Creates a blocking iterator for the given channel.
177181
*
178182
* @tparam Channel Type of channel being iterated.
179-
*
180183
* @param chan Reference to the channel this iterator will iterate over.
181-
*
182184
* @return A blocking iterator for the specified channel.
183185
*/
184186
template <typename Channel>

include/msd/channel.hpp

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ class closed_channel : public std::runtime_error {
3131
/**
3232
* @brief Thread-safe container for sharing data between threads.
3333
*
34-
* Implements a blocking input iterator.
34+
* - Not movable, not copyable.
35+
* - Includes a blocking input iterator.
3536
*
3637
* @tparam T The type of the elements.
3738
*/
@@ -85,9 +86,7 @@ class channel {
8586
* @brief Pushes an element into the channel.
8687
*
8788
* @tparam Type The type of the elements.
88-
*
8989
* @param value The element to be pushed into the channel.
90-
*
9190
* @return true If an element was successfully pushed into the channel.
9291
* @return false If the channel is closed.
9392
*/
@@ -103,7 +102,6 @@ class channel {
103102
}
104103

105104
queue_.push(std::forward<Type>(value));
106-
++size_;
107105
}
108106

109107
cnd_.notify_one();
@@ -115,7 +113,6 @@ class channel {
115113
* @brief Pops an element from the channel.
116114
*
117115
* @param out Reference to the variable where the popped element will be stored.
118-
*
119116
* @return true If an element was successfully read from the channel.
120117
* @return false If the channel is closed and empty.
121118
*/
@@ -125,13 +122,12 @@ class channel {
125122
std::unique_lock<std::mutex> lock{mtx_};
126123
waitBeforeRead(lock);
127124

128-
if (is_closed_ && size_ == 0) {
125+
if (is_closed_ && queue_.empty()) {
129126
return false;
130127
}
131128

132129
out = std::move(queue_.front());
133130
queue_.pop();
134-
--size_;
135131
}
136132

137133
cnd_.notify_one();
@@ -147,7 +143,7 @@ class channel {
147143
NODISCARD size_type size() const noexcept
148144
{
149145
std::unique_lock<std::mutex> lock{mtx_};
150-
return size_;
146+
return queue_.size();
151147
}
152148

153149
/**
@@ -159,7 +155,7 @@ class channel {
159155
NODISCARD bool empty() const noexcept
160156
{
161157
std::unique_lock<std::mutex> lock{mtx_};
162-
return size_ == 0;
158+
return queue_.empty();
163159
}
164160

165161
/**
@@ -195,7 +191,7 @@ class channel {
195191
NODISCARD bool drained() noexcept
196192
{
197193
std::unique_lock<std::mutex> lock{mtx_};
198-
return size_ == 0 && is_closed_;
194+
return queue_.empty() && is_closed_;
199195
}
200196

201197
/**
@@ -212,9 +208,6 @@ class channel {
212208
*/
213209
iterator end() noexcept { return blocking_iterator<channel<T>>{*this, true}; }
214210

215-
/**
216-
* Channel cannot be copied or moved.
217-
*/
218211
channel(const channel&) = delete;
219212
channel& operator=(const channel&) = delete;
220213
channel(channel&&) = delete;
@@ -223,21 +216,20 @@ class channel {
223216

224217
private:
225218
std::queue<T> queue_;
226-
std::size_t size_{0};
227219
const size_type cap_{0};
228220
mutable std::mutex mtx_;
229221
std::condition_variable cnd_;
230222
bool is_closed_{false};
231223

232224
void waitBeforeRead(std::unique_lock<std::mutex>& lock)
233225
{
234-
cnd_.wait(lock, [this]() { return size_ > 0 || is_closed_; });
226+
cnd_.wait(lock, [this]() { return !queue_.empty() || is_closed_; });
235227
};
236228

237229
void waitBeforeWrite(std::unique_lock<std::mutex>& lock)
238230
{
239-
if (cap_ > 0 && size_ == cap_) {
240-
cnd_.wait(lock, [this]() { return size_ < cap_; });
231+
if (cap_ > 0 && queue_.size() == cap_) {
232+
cnd_.wait(lock, [this]() { return queue_.size() < cap_; });
241233
}
242234
}
243235
};

include/msd/static_channel.hpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ namespace msd {
1616
/**
1717
* @brief Thread-safe container for sharing data between threads.
1818
*
19-
* Allocates elements on the stack.
20-
* Does not throw exceptions.
21-
* Implements a blocking input iterator.
19+
* - Allocates elements on the stack.
20+
* - Does not throw exceptions.
21+
* - Not movable, not copyable.
22+
* - Includes a blocking input iterator.
2223
*
2324
* @tparam T The type of the elements.
2425
* @tparam Capacity The maximum number of elements the channel can hold before blocking.
@@ -52,9 +53,7 @@ class static_channel {
5253
* @brief Pushes an element into the channel.
5354
*
5455
* @tparam Type The type of the elements.
55-
*
5656
* @param value The element to be pushed into the channel.
57-
*
5857
* @return true If an element was successfully pushed into the channel.
5958
* @return false If the channel is closed.
6059
*/
@@ -82,7 +81,6 @@ class static_channel {
8281
* @brief Pops an element from the channel.
8382
*
8483
* @param out Reference to the variable where the popped element will be stored.
85-
*
8684
* @return true If an element was successfully read from the channel.
8785
* @return false If the channel is closed and empty.
8886
*/
@@ -179,9 +177,6 @@ class static_channel {
179177
*/
180178
iterator end() noexcept { return blocking_iterator<static_channel<T, Capacity>>{*this, true}; }
181179

182-
/**
183-
* Channel cannot be copied or moved.
184-
*/
185180
static_channel(const static_channel&) = delete;
186181
static_channel& operator=(const static_channel&) = delete;
187182
static_channel(static_channel&&) = delete;

tests/blocking_iterator_test.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ TEST(BlockingWriterIteratorTest, WriteToChannelUsingBackInserter)
7070
*out = 20;
7171
*out = 30;
7272
channel.close();
73+
*out = 40; // Ignored because the channel is closed
7374
});
7475

7576
std::vector<int> results;

0 commit comments

Comments
 (0)