Commit 6736e5e07e75823186de581eea5fd0c3e4df7cbb

Authored by Patric Stout
1 parent e79fa386

feat: reduce memory footprint for subscriptions significantly

This is a breaking API change.

The whole interface of this library now uses "std::string_view"
instead of "std::string" / "std::string &". In result, we can
now promise to only copy data once thoughout the library.

For subscriptions, this is a copy once to read the data from the
socket into a buffer.

For publish, this is a copy once to write the data in a buffer
to send over the socket.

For publish, this doesn't change the memory footprint, as because
"std::string &" was already taking care of this. For subscriptions
however, it reduces the memory usage by a factor of three. And as
result it helps with the throughput.
README.md
1 # TrueMQTT - A modern C++ MQTT Client library 1 # TrueMQTT - A modern C++ MQTT Client library
2 2
3 This project is currently a Work In Progress. 3 This project is currently a Work In Progress.
4 -Although the basics are functional, it is untested. 4 +
  5 +All basic functionality is in there, but it is lacking some QoL functionalities, and it has not really been hardened / battle-tested yet.
5 6
6 ## Development 7 ## Development
7 8
8 ```bash 9 ```bash
9 mkdir build 10 mkdir build
10 cd build 11 cd build
11 -cmake .. -DBUILD_SHARED_LIBS=ON -DMIN_LOGGER_LEVEL=TRACE 12 +cmake .. -DBUILD_SHARED_LIBS=ON -DMIN_LOGGER_LEVEL=INFO
12 make -j$(nproc) 13 make -j$(nproc)
13 14
14 example/pubsub/truemqtt_pubsub 15 example/pubsub/truemqtt_pubsub
15 ``` 16 ```
  17 +
  18 +## Design choices
  19 +
  20 +### MQTT v3 only
  21 +
  22 +Although this is a contested choice, for now the library only supports MQTT v3.
  23 +There is added value in MQTT v5, but it comes with extra overhead, both in performance and memory.
  24 +
  25 +This library aims to supply an interface for the more common way of using MQTT, which is a simple publish / subscribe interface.
  26 +
  27 +In the future this might change, because, as said, MQTT v5 has solid additions, that might be worth delving in it.
  28 +
  29 +### Copy-once
  30 +
  31 +A packet that is received from a broker, is only copied once in memory (from `recv()` to an internal buffer).
  32 +All subscription callbacks get a `std::string_view` which is directly in this buffer.
  33 +
  34 +This way, the library only needs to allocate memory once, heavily reducing the memory footprint.
  35 +This also means the library can handle big payloads without issue.
  36 +
  37 +For publishing a similar approach is taken, and the topic / message is only copied once in an internal buffer.
  38 +The only exception here is when the client isn't connected to the broker (yet).
  39 +In this scenario, a copy of topic / message is made, and there will be two allocations for both, instead of one.
  40 +
  41 +Either way, this makes this library highly efficient in terms of memory usage.
  42 +
  43 +The drawback is that you have to be careful with your callbacks.
  44 +You always receive a `std::string_view`, that is only valid within that callback.
  45 +As soon as the callback returns, the memory becomes invalid.
  46 +
  47 +This means that if you need to keep the topic and/or message around, you need to make a copy.
  48 +
  49 +### QoS 0
  50 +
  51 +This library only supports QoS 0.
  52 +This is mainly because that is the only QoS I have ever used since using MQTT.
  53 +
  54 +The problem with other QoSes is that is is mostly pointless up till the point it is useful.
  55 +MQTT uses TCP, and as such, delivery over the socket is guaranteed if both sides are still alive.
  56 +In other words, QoS 1 doesn't add any guarantees in the normal situation, where lines / brokers aren't saturated.
  57 +When it does get saturated, QoS 1 becomes useful.
  58 +
  59 +But, there is a trade-off here.
  60 +If you always do QoS 1 for the cases where the line does get saturated, you put more pressure on the line in all cases, which results in a line that is saturated more quickly.
  61 +And in reality, it is very hard to recover from such scenarios anyway.
  62 +
  63 +MQTT 5 corrects this situation, by a bit of a cheat.
  64 +If you publish with QoS 1, but the TCP connection was working as expected, it in fact handles it as a QoS 0 request.
  65 +
  66 +For this reason, this library only supports QoS 0.
  67 +As added benefit, it makes for easier code, which is less like to have bugs / problems.
example/pubsub/main.cpp
@@ -14,10 +14,10 @@ int main() @@ -14,10 +14,10 @@ int main()
14 // Create a connection to the local broker. 14 // Create a connection to the local broker.
15 TrueMQTT::Client client("localhost", 1883, "test"); 15 TrueMQTT::Client client("localhost", 1883, "test");
16 16
17 - client.setLogger(TrueMQTT::Client::LogLevel::WARNING, [](TrueMQTT::Client::LogLevel level, std::string message) 17 + client.setLogger(TrueMQTT::Client::LogLevel::WARNING, [](TrueMQTT::Client::LogLevel level, std::string_view message)
18 { std::cout << "Log " << level << ": " << message << std::endl; }); 18 { std::cout << "Log " << level << ": " << message << std::endl; });
19 client.setPublishQueue(TrueMQTT::Client::PublishQueueType::FIFO, 10); 19 client.setPublishQueue(TrueMQTT::Client::PublishQueueType::FIFO, 10);
20 - client.setErrorCallback([](TrueMQTT::Client::Error error, std::string message) 20 + client.setErrorCallback([](TrueMQTT::Client::Error error, std::string_view message)
21 { std::cout << "Error " << error << ": " << message << std::endl; }); 21 { std::cout << "Error " << error << ": " << message << std::endl; });
22 client.setLastWill("example/pubsub/lastwill", "example pubsub finished", true); 22 client.setLastWill("example/pubsub/lastwill", "example pubsub finished", true);
23 23
@@ -26,23 +26,23 @@ int main() @@ -26,23 +26,23 @@ int main()
26 int stop = 0; 26 int stop = 0;
27 27
28 // Subscribe to the topic we will be publishing under in a bit. 28 // Subscribe to the topic we will be publishing under in a bit.
29 - client.subscribe("example/pubsub/test/subtest", [&stop](const std::string topic, const std::string payload) 29 + client.subscribe("example/pubsub/test/subtest", [&stop](const std::string_view topic, const std::string_view payload)
30 { 30 {
31 std::cout << "Received message on exact topic " << topic << ": " << payload << std::endl; 31 std::cout << "Received message on exact topic " << topic << ": " << payload << std::endl;
32 stop++; }); 32 stop++; });
33 - client.subscribe("example/pubsub/test/subtest", [&stop](const std::string topic, const std::string payload) 33 + client.subscribe("example/pubsub/test/subtest", [&stop](const std::string_view topic, const std::string_view payload)
34 { 34 {
35 - std::cout << "Received message on exact topic " << topic << ": " << payload << std::endl; 35 + std::cout << "Received message on exact topic " << topic << " again: " << payload << std::endl;
36 stop++; }); 36 stop++; });
37 - client.subscribe("example/pubsub/+/subtest", [&stop](const std::string topic, const std::string payload) 37 + client.subscribe("example/pubsub/+/subtest", [&stop](const std::string_view topic, const std::string_view payload)
38 { 38 {
39 std::cout << "Received message on single wildcard topic " << topic << ": " << payload << std::endl; 39 std::cout << "Received message on single wildcard topic " << topic << ": " << payload << std::endl;
40 stop++; }); 40 stop++; });
41 - client.subscribe("example/pubsub/test/#", [&stop](const std::string topic, const std::string payload) 41 + client.subscribe("example/pubsub/test/#", [&stop](const std::string_view topic, const std::string_view payload)
42 { 42 {
43 std::cout << "Received message on multi wildcard topic " << topic << ": " << payload << std::endl; 43 std::cout << "Received message on multi wildcard topic " << topic << ": " << payload << std::endl;
44 stop++; }); 44 stop++; });
45 - client.subscribe("example/pubsub/test/+", [&stop](const std::string topic, const std::string payload) 45 + client.subscribe("example/pubsub/test/+", [&stop](const std::string_view topic, const std::string_view payload)
46 { 46 {
47 /* Never actually called, as we unsubscribe a bit later */ }); 47 /* Never actually called, as we unsubscribe a bit later */ });
48 48
example/stress/main.cpp
@@ -14,10 +14,10 @@ int main() @@ -14,10 +14,10 @@ int main()
14 // Create a connection to the local broker. 14 // Create a connection to the local broker.
15 TrueMQTT::Client client("localhost", 1883, "test"); 15 TrueMQTT::Client client("localhost", 1883, "test");
16 16
17 - client.setLogger(TrueMQTT::Client::LogLevel::WARNING, [](TrueMQTT::Client::LogLevel level, std::string message) 17 + client.setLogger(TrueMQTT::Client::LogLevel::WARNING, [](TrueMQTT::Client::LogLevel level, std::string_view message)
18 { std::cout << "Log " << level << ": " << message << std::endl; }); 18 { std::cout << "Log " << level << ": " << message << std::endl; });
19 client.setPublishQueue(TrueMQTT::Client::PublishQueueType::FIFO, 100); 19 client.setPublishQueue(TrueMQTT::Client::PublishQueueType::FIFO, 100);
20 - client.setErrorCallback([](TrueMQTT::Client::Error error, std::string message) 20 + client.setErrorCallback([](TrueMQTT::Client::Error error, std::string_view message)
21 { std::cout << "Error " << error << ": " << message << std::endl; }); 21 { std::cout << "Error " << error << ": " << message << std::endl; });
22 client.setLastWill("test/lastwill", "example pubsub finished", true); 22 client.setLastWill("test/lastwill", "example pubsub finished", true);
23 23
@@ -30,11 +30,11 @@ int main() @@ -30,11 +30,11 @@ int main()
30 int64_t totalLatency = 0; 30 int64_t totalLatency = 0;
31 31
32 // Subscribe to the topic we are going to stress test. 32 // Subscribe to the topic we are going to stress test.
33 - client.subscribe("example/stress/+", [&received, &totalLatency](const std::string topic, const std::string payload) 33 + client.subscribe("example/stress/+", [&received, &totalLatency](const std::string_view topic, const std::string_view payload)
34 { 34 {
35 // Calculate the latency. 35 // Calculate the latency.
36 auto now = std::chrono::steady_clock::now(); 36 auto now = std::chrono::steady_clock::now();
37 - auto then = std::chrono::time_point<std::chrono::steady_clock>(std::chrono::microseconds(std::stoll(payload))); 37 + auto then = std::chrono::time_point<std::chrono::steady_clock>(std::chrono::microseconds(std::stoll(std::string(payload))));
38 auto latency = std::chrono::duration_cast<std::chrono::microseconds>(now - then).count(); 38 auto latency = std::chrono::duration_cast<std::chrono::microseconds>(now - then).count();
39 39
40 totalLatency += latency; 40 totalLatency += latency;
include/TrueMQTT.h
@@ -10,6 +10,7 @@ @@ -10,6 +10,7 @@
10 #include <chrono> 10 #include <chrono>
11 #include <functional> 11 #include <functional>
12 #include <memory> 12 #include <memory>
  13 +#include <string_view>
13 14
14 namespace TrueMQTT 15 namespace TrueMQTT
15 { 16 {
@@ -116,9 +117,9 @@ namespace TrueMQTT @@ -116,9 +117,9 @@ namespace TrueMQTT
116 * @param connection_backoff_max Maximum time between backoff attempts in seconds. 117 * @param connection_backoff_max Maximum time between backoff attempts in seconds.
117 * @param keep_alive_interval Interval in seconds between keep-alive messages. 118 * @param keep_alive_interval Interval in seconds between keep-alive messages.
118 */ 119 */
119 - Client(const std::string &host, 120 + Client(const std::string_view host,
120 int port, 121 int port,
121 - const std::string &client_id, 122 + const std::string_view client_id,
122 std::chrono::milliseconds connection_timeout = std::chrono::milliseconds(5000), 123 std::chrono::milliseconds connection_timeout = std::chrono::milliseconds(5000),
123 std::chrono::milliseconds connection_backoff = std::chrono::milliseconds(1000), 124 std::chrono::milliseconds connection_backoff = std::chrono::milliseconds(1000),
124 std::chrono::milliseconds connection_backoff_max = std::chrono::milliseconds(30000), 125 std::chrono::milliseconds connection_backoff_max = std::chrono::milliseconds(30000),
@@ -142,7 +143,7 @@ namespace TrueMQTT @@ -142,7 +143,7 @@ namespace TrueMQTT
142 * @note This library doesn't contain a logger, so you need to provide one. 143 * @note This library doesn't contain a logger, so you need to provide one.
143 * If this method is not called, no logging will be done. 144 * If this method is not called, no logging will be done.
144 */ 145 */
145 - void setLogger(LogLevel log_level, const std::function<void(LogLevel, std::string)> &logger) const; 146 + void setLogger(LogLevel log_level, const std::function<void(LogLevel, std::string_view)> &logger) const;
146 147
147 /** 148 /**
148 * @brief Set the last will message on the connection. 149 * @brief Set the last will message on the connection.
@@ -155,14 +156,14 @@ namespace TrueMQTT @@ -155,14 +156,14 @@ namespace TrueMQTT
155 * 156 *
156 * @note Cannot be called after \ref connect. 157 * @note Cannot be called after \ref connect.
157 */ 158 */
158 - void setLastWill(const std::string &topic, const std::string &message, bool retain) const; 159 + void setLastWill(const std::string_view topic, const std::string_view message, bool retain) const;
159 160
160 /** 161 /**
161 * @brief Set the error callback, called when any error occurs. 162 * @brief Set the error callback, called when any error occurs.
162 * 163 *
163 * @param callback The callback to call when an error occurs. 164 * @param callback The callback to call when an error occurs.
164 */ 165 */
165 - void setErrorCallback(const std::function<void(Error, std::string)> &callback) const; 166 + void setErrorCallback(const std::function<void(Error, std::string_view)> &callback) const;
166 167
167 /** 168 /**
168 * @brief Set the publish queue to use. 169 * @brief Set the publish queue to use.
@@ -247,7 +248,7 @@ namespace TrueMQTT @@ -247,7 +248,7 @@ namespace TrueMQTT
247 * in this case, but it is wise to back off for a while before sending something 248 * in this case, but it is wise to back off for a while before sending something
248 * again. 249 * again.
249 */ 250 */
250 - bool publish(const std::string &topic, const std::string &message, bool retain) const; 251 + bool publish(const std::string_view topic, const std::string_view message, bool retain) const;
251 252
252 /** 253 /**
253 * @brief Subscribe to a topic, and call the callback function when a message arrives. 254 * @brief Subscribe to a topic, and call the callback function when a message arrives.
@@ -261,6 +262,9 @@ namespace TrueMQTT @@ -261,6 +262,9 @@ namespace TrueMQTT
261 * @param topic The topic to subscribe to. 262 * @param topic The topic to subscribe to.
262 * @param callback The callback to call when a message arrives on this topic. 263 * @param callback The callback to call when a message arrives on this topic.
263 * 264 *
  265 + * @note The callback receives a string_view for topic/message, which is only valid
  266 + * for the duration of the callback. If you need to retain the value of longer,
  267 + * make sure to copy the content.
264 * @note Subscription can overlap, even on the exact same topic. All callbacks that 268 * @note Subscription can overlap, even on the exact same topic. All callbacks that
265 * match the topic will be called. 269 * match the topic will be called.
266 * @note Depending on the broker, overlapping subscriptions can trigger one or more 270 * @note Depending on the broker, overlapping subscriptions can trigger one or more
@@ -279,7 +283,7 @@ namespace TrueMQTT @@ -279,7 +283,7 @@ namespace TrueMQTT
279 * moment the connection to the broker is established, and there are messages in the 283 * moment the connection to the broker is established, and there are messages in the
280 * publish queue and/or subscriptions. 284 * publish queue and/or subscriptions.
281 */ 285 */
282 - void subscribe(const std::string &topic, const std::function<void(std::string, std::string)> &callback) const; 286 + void subscribe(const std::string_view topic, const std::function<void(std::string_view, std::string_view)> &callback) const;
283 287
284 /** 288 /**
285 * @brief Unsubscribe from a topic. 289 * @brief Unsubscribe from a topic.
@@ -295,7 +299,7 @@ namespace TrueMQTT @@ -295,7 +299,7 @@ namespace TrueMQTT
295 * moment the connection to the broker is established, and there are messages in the 299 * moment the connection to the broker is established, and there are messages in the
296 * publish queue and/or subscriptions. 300 * publish queue and/or subscriptions.
297 */ 301 */
298 - void unsubscribe(const std::string &topic) const; 302 + void unsubscribe(const std::string_view topic) const;
299 303
300 private: 304 private:
301 // Private implementation 305 // Private implementation
src/Client.cpp
@@ -13,9 +13,9 @@ @@ -13,9 +13,9 @@
13 13
14 #include <sstream> 14 #include <sstream>
15 15
16 -TrueMQTT::Client::Client(const std::string &host, 16 +TrueMQTT::Client::Client(const std::string_view host,
17 int port, 17 int port,
18 - const std::string &client_id, 18 + const std::string_view client_id,
19 std::chrono::milliseconds connection_timeout, 19 std::chrono::milliseconds connection_timeout,
20 std::chrono::milliseconds connection_backoff, 20 std::chrono::milliseconds connection_backoff,
21 std::chrono::milliseconds connection_backoff_max, 21 std::chrono::milliseconds connection_backoff_max,
@@ -33,9 +33,9 @@ TrueMQTT::Client::~Client() @@ -33,9 +33,9 @@ TrueMQTT::Client::~Client()
33 disconnect(); 33 disconnect();
34 } 34 }
35 35
36 -TrueMQTT::Client::Impl::Impl(const std::string &host, 36 +TrueMQTT::Client::Impl::Impl(const std::string_view host,
37 int port, 37 int port,
38 - const std::string &client_id, 38 + const std::string_view client_id,
39 std::chrono::milliseconds connection_timeout, 39 std::chrono::milliseconds connection_timeout,
40 std::chrono::milliseconds connection_backoff, 40 std::chrono::milliseconds connection_backoff,
41 std::chrono::milliseconds connection_backoff_max, 41 std::chrono::milliseconds connection_backoff_max,
@@ -54,7 +54,7 @@ TrueMQTT::Client::Impl::~Impl() @@ -54,7 +54,7 @@ TrueMQTT::Client::Impl::~Impl()
54 { 54 {
55 } 55 }
56 56
57 -void TrueMQTT::Client::setLogger(Client::LogLevel log_level, const std::function<void(Client::LogLevel, std::string)> &logger) const 57 +void TrueMQTT::Client::setLogger(Client::LogLevel log_level, const std::function<void(Client::LogLevel, std::string_view)> &logger) const
58 { 58 {
59 LOG_TRACE(m_impl, "Setting logger to log level " + std::to_string(log_level)); 59 LOG_TRACE(m_impl, "Setting logger to log level " + std::to_string(log_level));
60 60
@@ -64,7 +64,7 @@ void TrueMQTT::Client::setLogger(Client::LogLevel log_level, const std::function @@ -64,7 +64,7 @@ void TrueMQTT::Client::setLogger(Client::LogLevel log_level, const std::function
64 LOG_DEBUG(m_impl, "Log level now on " + std::to_string(m_impl->m_log_level)); 64 LOG_DEBUG(m_impl, "Log level now on " + std::to_string(m_impl->m_log_level));
65 } 65 }
66 66
67 -void TrueMQTT::Client::setLastWill(const std::string &topic, const std::string &message, bool retain) const 67 +void TrueMQTT::Client::setLastWill(const std::string_view topic, const std::string_view message, bool retain) const
68 { 68 {
69 if (m_impl->m_state != Client::Impl::State::DISCONNECTED) 69 if (m_impl->m_state != Client::Impl::State::DISCONNECTED)
70 { 70 {
@@ -72,14 +72,14 @@ void TrueMQTT::Client::setLastWill(const std::string &amp;topic, const std::string &amp; @@ -72,14 +72,14 @@ void TrueMQTT::Client::setLastWill(const std::string &amp;topic, const std::string &amp;
72 return; 72 return;
73 } 73 }
74 74
75 - LOG_TRACE(m_impl, "Setting last will to topic " + topic + " with message " + message + " and retain " + std::to_string(retain)); 75 + LOG_TRACE(m_impl, "Setting last will to topic " + std::string(topic) + " with message " + std::string(message) + " and retain " + std::to_string(retain));
76 76
77 m_impl->m_last_will_topic = topic; 77 m_impl->m_last_will_topic = topic;
78 m_impl->m_last_will_message = message; 78 m_impl->m_last_will_message = message;
79 m_impl->m_last_will_retain = retain; 79 m_impl->m_last_will_retain = retain;
80 } 80 }
81 81
82 -void TrueMQTT::Client::setErrorCallback(const std::function<void(Error, std::string)> &callback) const 82 +void TrueMQTT::Client::setErrorCallback(const std::function<void(Error, std::string_view)> &callback) const
83 { 83 {
84 LOG_TRACE(m_impl, "Setting error callback"); 84 LOG_TRACE(m_impl, "Setting error callback");
85 85
@@ -144,11 +144,11 @@ void TrueMQTT::Client::disconnect() const @@ -144,11 +144,11 @@ void TrueMQTT::Client::disconnect() const
144 m_impl->disconnect(); 144 m_impl->disconnect();
145 } 145 }
146 146
147 -bool TrueMQTT::Client::publish(const std::string &topic, const std::string &message, bool retain) const 147 +bool TrueMQTT::Client::publish(const std::string_view topic, const std::string_view message, bool retain) const
148 { 148 {
149 std::scoped_lock lock(m_impl->m_state_mutex); 149 std::scoped_lock lock(m_impl->m_state_mutex);
150 150
151 - LOG_DEBUG(m_impl, "Publishing message on topic '" + topic + "': " + message + " (" + (retain ? "retained" : "not retained") + ")"); 151 + LOG_DEBUG(m_impl, "Publishing message on topic '" + std::string(topic) + "': " + std::string(message) + " (" + (retain ? "retained" : "not retained") + ")");
152 152
153 switch (m_impl->m_state) 153 switch (m_impl->m_state)
154 { 154 {
@@ -164,7 +164,7 @@ bool TrueMQTT::Client::publish(const std::string &amp;topic, const std::string &amp;mess @@ -164,7 +164,7 @@ bool TrueMQTT::Client::publish(const std::string &amp;topic, const std::string &amp;mess
164 return false; 164 return false;
165 } 165 }
166 166
167 -void TrueMQTT::Client::subscribe(const std::string &topic, const std::function<void(std::string, std::string)> &callback) const 167 +void TrueMQTT::Client::subscribe(const std::string_view topic, const std::function<void(std::string_view, std::string_view)> &callback) const
168 { 168 {
169 std::scoped_lock lock(m_impl->m_state_mutex); 169 std::scoped_lock lock(m_impl->m_state_mutex);
170 170
@@ -174,23 +174,44 @@ void TrueMQTT::Client::subscribe(const std::string &amp;topic, const std::function&lt;v @@ -174,23 +174,44 @@ void TrueMQTT::Client::subscribe(const std::string &amp;topic, const std::function&lt;v
174 return; 174 return;
175 } 175 }
176 176
177 - LOG_DEBUG(m_impl, "Subscribing to topic '" + topic + "'"); 177 + LOG_DEBUG(m_impl, "Subscribing to topic '" + std::string(topic) + "'");
178 178
179 - // Split the topic on /, to find each part.  
180 - std::string part;  
181 - std::stringstream stopic(topic);  
182 - std::getline(stopic, part, '/');  
183 -  
184 - // Find the root node, and walk down till we find the leaf node.  
185 - Client::Impl::SubscriptionPart *subscriptions = &m_impl->m_subscriptions.try_emplace(part).first->second;  
186 - while (std::getline(stopic, part, '/')) 179 + // Find where in the tree the callback for this subscription should be added.
  180 + Client::Impl::SubscriptionPart *subscriptions = nullptr;
  181 + std::string_view topic_search = topic;
  182 + while (true)
187 { 183 {
188 - subscriptions = &subscriptions->children.try_emplace(part).first->second; 184 + std::string_view part = topic_search;
  185 +
  186 + // Find the next part of the topic.
  187 + auto pos = topic_search.find('/');
  188 + if (pos != std::string_view::npos)
  189 + {
  190 + part = topic_search.substr(0, pos);
  191 + topic_search.remove_prefix(pos + 1);
  192 + }
  193 +
  194 + // Find the next subscription in the tree.
  195 + if (subscriptions == nullptr)
  196 + {
  197 + subscriptions = &m_impl->m_subscriptions.try_emplace(std::string(part)).first->second;
  198 + }
  199 + else
  200 + {
  201 + subscriptions = &subscriptions->children.try_emplace(std::string(part)).first->second;
  202 + }
  203 +
  204 + // If this was the last element, we're done.
  205 + if (pos == std::string_view::npos)
  206 + {
  207 + break;
  208 + }
189 } 209 }
  210 +
190 // Add the callback to the leaf node. 211 // Add the callback to the leaf node.
191 subscriptions->callbacks.push_back(callback); 212 subscriptions->callbacks.push_back(callback);
192 213
193 - m_impl->m_subscription_topics.insert(topic); 214 + m_impl->m_subscription_topics.insert(std::string(topic));
194 if (m_impl->m_state == Client::Impl::State::CONNECTED) 215 if (m_impl->m_state == Client::Impl::State::CONNECTED)
195 { 216 {
196 if (!m_impl->sendSubscribe(topic)) 217 if (!m_impl->sendSubscribe(topic))
@@ -202,7 +223,7 @@ void TrueMQTT::Client::subscribe(const std::string &amp;topic, const std::function&lt;v @@ -202,7 +223,7 @@ void TrueMQTT::Client::subscribe(const std::string &amp;topic, const std::function&lt;v
202 } 223 }
203 } 224 }
204 225
205 -void TrueMQTT::Client::unsubscribe(const std::string &topic) const 226 +void TrueMQTT::Client::unsubscribe(const std::string_view topic) const
206 { 227 {
207 std::scoped_lock lock(m_impl->m_state_mutex); 228 std::scoped_lock lock(m_impl->m_state_mutex);
208 229
@@ -212,34 +233,63 @@ void TrueMQTT::Client::unsubscribe(const std::string &amp;topic) const @@ -212,34 +233,63 @@ void TrueMQTT::Client::unsubscribe(const std::string &amp;topic) const
212 return; 233 return;
213 } 234 }
214 235
215 - LOG_DEBUG(m_impl, "Unsubscribing from topic '" + topic + "'"); 236 + if (m_impl->m_subscription_topics.find(topic) == m_impl->m_subscription_topics.end())
  237 + {
  238 + LOG_ERROR(m_impl, "Cannot unsubscribe from topic '" + std::string(topic) + "' because we are not subscribed to it");
  239 + return;
  240 + }
  241 +
  242 + LOG_DEBUG(m_impl, "Unsubscribing from topic '" + std::string(topic) + "'");
216 243
217 - // Split the topic on /, to find each part.  
218 - std::string part;  
219 - std::stringstream stopic(topic);  
220 - std::getline(stopic, part, '/'); 244 + std::vector<std::tuple<std::string_view, Client::Impl::SubscriptionPart *>> reverse;
221 245
222 - // Find the root node, and walk down till we find the leaf node.  
223 - std::vector<std::tuple<std::string, Client::Impl::SubscriptionPart *>> reverse;  
224 - Client::Impl::SubscriptionPart *subscriptions = &m_impl->m_subscriptions[part];  
225 - reverse.emplace_back(part, subscriptions);  
226 - while (std::getline(stopic, part, '/')) 246 + // Find where in the tree the callback for this subscription should be removed.
  247 + Client::Impl::SubscriptionPart *subscriptions = nullptr;
  248 + std::string_view topic_search = topic;
  249 + while (true)
227 { 250 {
228 - subscriptions = &subscriptions->children[part]; 251 + std::string_view part = topic_search;
  252 +
  253 + // Find the next part of the topic.
  254 + auto pos = topic_search.find('/');
  255 + if (pos != std::string_view::npos)
  256 + {
  257 + part = topic_search.substr(0, pos);
  258 + topic_search.remove_prefix(pos + 1);
  259 + }
  260 +
  261 + // Find the next subscription in the tree.
  262 + if (subscriptions == nullptr)
  263 + {
  264 + subscriptions = &m_impl->m_subscriptions.find(part)->second;
  265 + }
  266 + else
  267 + {
  268 + subscriptions = &subscriptions->children.find(part)->second;
  269 + }
  270 +
  271 + // Update the reverse lookup.
229 reverse.emplace_back(part, subscriptions); 272 reverse.emplace_back(part, subscriptions);
  273 +
  274 + // If this was the last element, we're done.
  275 + if (pos == std::string_view::npos)
  276 + {
  277 + break;
  278 + }
230 } 279 }
  280 +
231 // Clear the callbacks in the leaf node. 281 // Clear the callbacks in the leaf node.
232 subscriptions->callbacks.clear(); 282 subscriptions->callbacks.clear();
233 283
234 // Bookkeeping: remove any empty nodes. 284 // Bookkeeping: remove any empty nodes.
235 // Otherwise we will slowly grow in memory if a user does a lot of unsubscribes 285 // Otherwise we will slowly grow in memory if a user does a lot of unsubscribes
236 // on different topics. 286 // on different topics.
237 - std::string remove_next = ""; 287 + std::string_view remove_next = "";
238 for (auto it = reverse.rbegin(); it != reverse.rend(); it++) 288 for (auto it = reverse.rbegin(); it != reverse.rend(); it++)
239 { 289 {
240 if (!remove_next.empty()) 290 if (!remove_next.empty())
241 { 291 {
242 - std::get<1>(*it)->children.erase(remove_next); 292 + std::get<1>(*it)->children.erase(std::get<1>(*it)->children.find(remove_next));
243 remove_next = ""; 293 remove_next = "";
244 } 294 }
245 295
@@ -250,10 +300,10 @@ void TrueMQTT::Client::unsubscribe(const std::string &amp;topic) const @@ -250,10 +300,10 @@ void TrueMQTT::Client::unsubscribe(const std::string &amp;topic) const
250 } 300 }
251 if (!remove_next.empty()) 301 if (!remove_next.empty())
252 { 302 {
253 - m_impl->m_subscriptions.erase(remove_next); 303 + m_impl->m_subscriptions.erase(m_impl->m_subscriptions.find(remove_next));
254 } 304 }
255 305
256 - m_impl->m_subscription_topics.erase(topic); 306 + m_impl->m_subscription_topics.erase(m_impl->m_subscription_topics.find(topic));
257 if (m_impl->m_state == Client::Impl::State::CONNECTED) 307 if (m_impl->m_state == Client::Impl::State::CONNECTED)
258 { 308 {
259 if (!m_impl->sendUnsubscribe(topic)) 309 if (!m_impl->sendUnsubscribe(topic))
@@ -312,7 +362,7 @@ void TrueMQTT::Client::Impl::connectionStateChange(bool connected) @@ -312,7 +362,7 @@ void TrueMQTT::Client::Impl::connectionStateChange(bool connected)
312 } 362 }
313 } 363 }
314 364
315 -bool TrueMQTT::Client::Impl::toPublishQueue(const std::string &topic, const std::string &message, bool retain) 365 +bool TrueMQTT::Client::Impl::toPublishQueue(const std::string_view topic, const std::string_view message, bool retain)
316 { 366 {
317 if (m_state != Client::Impl::State::CONNECTING) 367 if (m_state != Client::Impl::State::CONNECTING)
318 { 368 {
@@ -346,26 +396,34 @@ bool TrueMQTT::Client::Impl::toPublishQueue(const std::string &amp;topic, const std: @@ -346,26 +396,34 @@ bool TrueMQTT::Client::Impl::toPublishQueue(const std::string &amp;topic, const std:
346 return true; 396 return true;
347 } 397 }
348 398
349 -void TrueMQTT::Client::Impl::findSubscriptionMatch(std::vector<std::function<void(std::string, std::string)>> &matching_callbacks, const std::map<std::string, Client::Impl::SubscriptionPart> &subscriptions, std::deque<std::string> &parts) 399 +void TrueMQTT::Client::Impl::findSubscriptionMatch(std::string_view topic, std::string_view message, std::string_view topic_search, const std::map<std::string, Client::Impl::SubscriptionPart, std::less<>> &subscriptions)
350 { 400 {
351 - // If we reached the end of the topic, do nothing anymore.  
352 - if (parts.empty()) 401 + std::string_view part = topic_search;
  402 +
  403 + // Find the next part of the topic.
  404 + auto pos = topic_search.find('/');
  405 + if (pos != std::string_view::npos)
353 { 406 {
354 - return; 407 + part = topic_search.substr(0, pos);
  408 + topic_search.remove_prefix(pos + 1);
355 } 409 }
356 410
357 - LOG_TRACE(this, "Finding subscription match for part '" + parts.front() + "'");  
358 -  
359 // Find the match based on the part. 411 // Find the match based on the part.
360 - auto it = subscriptions.find(parts.front()); 412 + auto it = subscriptions.find(part);
361 if (it != subscriptions.end()) 413 if (it != subscriptions.end())
362 { 414 {
363 - LOG_TRACE(this, "Found subscription match for part '" + parts.front() + "' with " + std::to_string(it->second.callbacks.size()) + " callbacks"); 415 + LOG_TRACE(this, "Found subscription match for part '" + std::string(part) + "' with " + std::to_string(it->second.callbacks.size()) + " callbacks");
364 416
365 - matching_callbacks.insert(matching_callbacks.end(), it->second.callbacks.begin(), it->second.callbacks.end()); 417 + for (const auto &callback : it->second.callbacks)
  418 + {
  419 + callback(topic, message);
  420 + }
366 421
367 - std::deque<std::string> remaining_parts(parts.begin() + 1, parts.end());  
368 - findSubscriptionMatch(matching_callbacks, it->second.children, remaining_parts); 422 + // Recursively find the match for the next part if we didn't reach the end.
  423 + if (pos != std::string_view::npos)
  424 + {
  425 + findSubscriptionMatch(topic, message, topic_search, it->second.children);
  426 + }
369 } 427 }
370 428
371 // Find the match if this part is a wildcard. 429 // Find the match if this part is a wildcard.
@@ -374,10 +432,16 @@ void TrueMQTT::Client::Impl::findSubscriptionMatch(std::vector&lt;std::function&lt;voi @@ -374,10 +432,16 @@ void TrueMQTT::Client::Impl::findSubscriptionMatch(std::vector&lt;std::function&lt;voi
374 { 432 {
375 LOG_TRACE(this, "Found subscription match for '+' with " + std::to_string(it->second.callbacks.size()) + " callbacks"); 433 LOG_TRACE(this, "Found subscription match for '+' with " + std::to_string(it->second.callbacks.size()) + " callbacks");
376 434
377 - matching_callbacks.insert(matching_callbacks.end(), it->second.callbacks.begin(), it->second.callbacks.end()); 435 + for (const auto &callback : it->second.callbacks)
  436 + {
  437 + callback(topic, message);
  438 + }
378 439
379 - std::deque<std::string> remaining_parts(parts.begin() + 1, parts.end());  
380 - findSubscriptionMatch(matching_callbacks, it->second.children, remaining_parts); 440 + // Recursively find the match for the next part if we didn't reach the end.
  441 + if (pos != std::string_view::npos)
  442 + {
  443 + findSubscriptionMatch(topic, message, topic_search, it->second.children);
  444 + }
381 } 445 }
382 446
383 // Find the match if the remaining is a wildcard. 447 // Find the match if the remaining is a wildcard.
@@ -386,40 +450,27 @@ void TrueMQTT::Client::Impl::findSubscriptionMatch(std::vector&lt;std::function&lt;voi @@ -386,40 +450,27 @@ void TrueMQTT::Client::Impl::findSubscriptionMatch(std::vector&lt;std::function&lt;voi
386 { 450 {
387 LOG_TRACE(this, "Found subscription match for '#' with " + std::to_string(it->second.callbacks.size()) + " callbacks"); 451 LOG_TRACE(this, "Found subscription match for '#' with " + std::to_string(it->second.callbacks.size()) + " callbacks");
388 452
389 - matching_callbacks.insert(matching_callbacks.end(), it->second.callbacks.begin(), it->second.callbacks.end()); 453 + for (const auto &callback : it->second.callbacks)
  454 + {
  455 + callback(topic, message);
  456 + }
  457 +
390 // No more recursion here, as we implicit consume the rest of the parts too. 458 // No more recursion here, as we implicit consume the rest of the parts too.
391 } 459 }
392 } 460 }
393 461
394 -void TrueMQTT::Client::Impl::messageReceived(std::string topic, std::string message) 462 +void TrueMQTT::Client::Impl::messageReceived(std::string_view topic, std::string_view message)
395 { 463 {
396 - LOG_TRACE(this, "Message received on topic '" + topic + "': " + message);  
397 -  
398 - // Split the topic on the / in parts.  
399 - std::string part;  
400 - std::stringstream stopic(topic);  
401 - std::deque<std::string> parts;  
402 - while (std::getline(stopic, part, '/'))  
403 - {  
404 - parts.emplace_back(part);  
405 - }  
406 -  
407 - // Find the matching subscription(s) with recursion.  
408 - std::vector<std::function<void(std::string, std::string)>> matching_callbacks;  
409 - findSubscriptionMatch(matching_callbacks, m_subscriptions, parts); 464 + std::scoped_lock lock(m_state_mutex);
410 465
411 - LOG_TRACE(this, "Found " + std::to_string(matching_callbacks.size()) + " subscription(s) for topic '" + topic + "'"); 466 + LOG_TRACE(this, "Message received on topic '" + std::string(topic) + "': " + std::string(message));
412 467
413 - if (matching_callbacks.size() == 1) 468 + if (m_state != State::CONNECTED)
414 { 469 {
415 - // For a single callback there is no need to copy the topic/message.  
416 - matching_callbacks[0](std::move(topic), std::move(message));  
417 - }  
418 - else  
419 - {  
420 - for (const auto &callback : matching_callbacks)  
421 - {  
422 - callback(topic, message);  
423 - } 470 + // This happens easily when the subscribed to a really busy topic and you disconnect.
  471 + LOG_ERROR(this, "Received message while not connected");
  472 + return;
424 } 473 }
  474 +
  475 + findSubscriptionMatch(topic, message, topic, m_subscriptions);
425 } 476 }
src/ClientImpl.h
@@ -22,9 +22,9 @@ @@ -22,9 +22,9 @@
22 class TrueMQTT::Client::Impl 22 class TrueMQTT::Client::Impl
23 { 23 {
24 public: 24 public:
25 - Impl(const std::string &host, 25 + Impl(const std::string_view host,
26 int port, 26 int port,
27 - const std::string &client_id, 27 + const std::string_view client_id,
28 std::chrono::milliseconds connection_timeout, 28 std::chrono::milliseconds connection_timeout,
29 std::chrono::milliseconds connection_backoff, 29 std::chrono::milliseconds connection_backoff,
30 std::chrono::milliseconds connection_backoff_max, 30 std::chrono::milliseconds connection_backoff_max,
@@ -41,20 +41,20 @@ public: @@ -41,20 +41,20 @@ public:
41 class SubscriptionPart 41 class SubscriptionPart
42 { 42 {
43 public: 43 public:
44 - std::map<std::string, SubscriptionPart> children;  
45 - std::vector<std::function<void(std::string, std::string)>> callbacks; 44 + std::map<std::string, SubscriptionPart, std::less<>> children;
  45 + std::vector<std::function<void(std::string_view, std::string_view)>> callbacks;
46 }; 46 };
47 47
48 - void connect(); ///< Connect to the broker.  
49 - void disconnect(); ///< Disconnect from the broker.  
50 - bool sendPublish(const std::string &topic, const std::string &message, bool retain); ///< Send a publish message to the broker.  
51 - bool sendSubscribe(const std::string &topic); ///< Send a subscribe message to the broker.  
52 - bool sendUnsubscribe(const std::string &topic); ///< Send an unsubscribe message to the broker.  
53 - void connectionStateChange(bool connected); ///< Called when a connection goes from CONNECTING state to CONNECTED state or visa versa.  
54 - bool toPublishQueue(const std::string &topic, const std::string &message, bool retain); ///< Add a publish message to the publish queue.  
55 - void messageReceived(std::string topic, std::string message); ///< Called when a message is received from the broker. 48 + void connect(); ///< Connect to the broker.
  49 + void disconnect(); ///< Disconnect from the broker.
  50 + bool sendPublish(const std::string_view topic, const std::string_view message, bool retain); ///< Send a publish message to the broker.
  51 + bool sendSubscribe(const std::string_view topic); ///< Send a subscribe message to the broker.
  52 + bool sendUnsubscribe(const std::string_view topic); ///< Send an unsubscribe message to the broker.
  53 + void connectionStateChange(bool connected); ///< Called when a connection goes from CONNECTING state to CONNECTED state or visa versa.
  54 + bool toPublishQueue(const std::string_view topic, const std::string_view message, bool retain); ///< Add a publish message to the publish queue.
  55 + void messageReceived(std::string_view topic, std::string_view message); ///< Called when a message is received from the broker.
56 56
57 - void findSubscriptionMatch(std::vector<std::function<void(std::string, std::string)>> &callbacks, const std::map<std::string, SubscriptionPart> &subscriptions, std::deque<std::string> &parts); ///< Recursive function to find any matching subscription based on parts. 57 + void findSubscriptionMatch(std::string_view topic, std::string_view message, std::string_view topic_search, const std::map<std::string, Client::Impl::SubscriptionPart, std::less<>> &subscriptions); ///< Recursive function to find any matching subscription based on parts.
58 58
59 State m_state = State::DISCONNECTED; ///< The current state of the client. 59 State m_state = State::DISCONNECTED; ///< The current state of the client.
60 std::mutex m_state_mutex; ///< Mutex to protect state changes. 60 std::mutex m_state_mutex; ///< Mutex to protect state changes.
@@ -67,14 +67,14 @@ public: @@ -67,14 +67,14 @@ public:
67 std::chrono::milliseconds m_connection_backoff_max; ///< Maximum time between backoff attempts in seconds. 67 std::chrono::milliseconds m_connection_backoff_max; ///< Maximum time between backoff attempts in seconds.
68 std::chrono::milliseconds m_keep_alive_interval; ///< Interval in seconds between keep-alive messages. 68 std::chrono::milliseconds m_keep_alive_interval; ///< Interval in seconds between keep-alive messages.
69 69
70 - Client::LogLevel m_log_level = Client::LogLevel::NONE; ///< The log level to use.  
71 - std::function<void(Client::LogLevel, std::string)> m_logger = [](Client::LogLevel, std::string) { /* empty */ }; ///< Logger callback. 70 + Client::LogLevel m_log_level = Client::LogLevel::NONE; ///< The log level to use.
  71 + std::function<void(Client::LogLevel, std::string_view)> m_logger = [](Client::LogLevel, std::string_view) { /* empty */ }; ///< Logger callback.
72 72
73 std::string m_last_will_topic = ""; ///< Topic to publish the last will message to. 73 std::string m_last_will_topic = ""; ///< Topic to publish the last will message to.
74 std::string m_last_will_message = ""; ///< Message to publish on the last will topic. 74 std::string m_last_will_message = ""; ///< Message to publish on the last will topic.
75 bool m_last_will_retain = false; ///< Whether to retain the last will message. 75 bool m_last_will_retain = false; ///< Whether to retain the last will message.
76 76
77 - std::function<void(Error, std::string)> m_error_callback = [](Error, std::string) { /* empty */ }; ///< Error callback. 77 + std::function<void(Error, std::string_view)> m_error_callback = [](Error, std::string_view) { /* empty */ }; ///< Error callback.
78 78
79 Client::PublishQueueType m_publish_queue_type = Client::PublishQueueType::DROP; ///< The type of queue to use for the publish queue. 79 Client::PublishQueueType m_publish_queue_type = Client::PublishQueueType::DROP; ///< The type of queue to use for the publish queue.
80 size_t m_publish_queue_size = -1; ///< Size of the publish queue. 80 size_t m_publish_queue_size = -1; ///< Size of the publish queue.
@@ -82,8 +82,8 @@ public: @@ -82,8 +82,8 @@ public:
82 82
83 size_t m_send_queue_size = 1000; ///< Size of the send queue. 83 size_t m_send_queue_size = 1000; ///< Size of the send queue.
84 84
85 - std::set<std::string> m_subscription_topics; ///< Flat list of topics the client is subscribed to.  
86 - std::map<std::string, SubscriptionPart> m_subscriptions; ///< Tree of active subscriptions build up from the parts on the topic. 85 + std::set<std::string, std::less<>> m_subscription_topics; ///< Flat list of topics the client is subscribed to.
  86 + std::map<std::string, SubscriptionPart, std::less<>> m_subscriptions; ///< Tree of active subscriptions build up from the parts on the topic.
87 87
88 class Connection; 88 class Connection;
89 std::unique_ptr<Connection> m_connection; ///< Connection to the broker. 89 std::unique_ptr<Connection> m_connection; ///< Connection to the broker.
src/Packet.cpp
@@ -177,19 +177,19 @@ bool TrueMQTT::Client::Impl::Connection::recvLoop() @@ -177,19 +177,19 @@ bool TrueMQTT::Client::Impl::Connection::recvLoop()
177 } 177 }
178 case Packet::PacketType::PUBLISH: 178 case Packet::PacketType::PUBLISH:
179 { 179 {
180 - std::string topic; 180 + std::string_view topic;
181 if (!packet.read_string(topic)) 181 if (!packet.read_string(topic))
182 { 182 {
183 LOG_ERROR(&m_impl, "Malformed packet received, closing connection"); 183 LOG_ERROR(&m_impl, "Malformed packet received, closing connection");
184 return false; 184 return false;
185 } 185 }
186 186
187 - std::string message; 187 + std::string_view message;
188 packet.read_remaining(message); 188 packet.read_remaining(message);
189 189
190 - LOG_DEBUG(&m_impl, "Received PUBLISH with topic " + topic + ": " + message); 190 + LOG_DEBUG(&m_impl, "Received PUBLISH with topic " + std::string(topic) + ": " + std::string(message));
191 191
192 - m_impl.messageReceived(std::move(topic), std::move(message)); 192 + m_impl.messageReceived(topic, message);
193 break; 193 break;
194 } 194 }
195 case Packet::PacketType::SUBACK: 195 case Packet::PacketType::SUBACK:
@@ -288,7 +288,6 @@ bool TrueMQTT::Client::Impl::Connection::send(Packet packet, bool has_priority) @@ -288,7 +288,6 @@ bool TrueMQTT::Client::Impl::Connection::send(Packet packet, bool has_priority)
288 { 288 {
289 m_send_queue.push_back(std::move(packet)); 289 m_send_queue.push_back(std::move(packet));
290 } 290 }
291 -  
292 } 291 }
293 // Notify the write thread that there is a new packet. 292 // Notify the write thread that there is a new packet.
294 m_send_queue_cv.notify_one(); 293 m_send_queue_cv.notify_one();
@@ -374,9 +373,9 @@ bool TrueMQTT::Client::Impl::Connection::sendPingRequest() @@ -374,9 +373,9 @@ bool TrueMQTT::Client::Impl::Connection::sendPingRequest()
374 return send(std::move(packet), true); 373 return send(std::move(packet), true);
375 } 374 }
376 375
377 -bool TrueMQTT::Client::Impl::sendPublish(const std::string &topic, const std::string &message, bool retain) 376 +bool TrueMQTT::Client::Impl::sendPublish(const std::string_view topic, const std::string_view message, bool retain)
378 { 377 {
379 - LOG_TRACE(this, "Sending PUBLISH packet to topic '" + topic + "': " + message + " (" + (retain ? "retained" : "not retained") + ")"); 378 + LOG_TRACE(this, "Sending PUBLISH packet to topic '" + std::string(topic) + "': " + std::string(message) + " (" + (retain ? "retained" : "not retained") + ")");
380 379
381 uint8_t flags = 0; 380 uint8_t flags = 0;
382 flags |= (retain ? 1 : 0) << 0; // Retain 381 flags |= (retain ? 1 : 0) << 0; // Retain
@@ -386,14 +385,14 @@ bool TrueMQTT::Client::Impl::sendPublish(const std::string &amp;topic, const std::st @@ -386,14 +385,14 @@ bool TrueMQTT::Client::Impl::sendPublish(const std::string &amp;topic, const std::st
386 Packet packet(Packet::PacketType::PUBLISH, flags); 385 Packet packet(Packet::PacketType::PUBLISH, flags);
387 386
388 packet.write_string(topic); 387 packet.write_string(topic);
389 - packet.write(message.c_str(), message.size()); 388 + packet.write(message.data(), message.size());
390 389
391 return m_connection->send(std::move(packet)); 390 return m_connection->send(std::move(packet));
392 } 391 }
393 392
394 -bool TrueMQTT::Client::Impl::sendSubscribe(const std::string &topic) 393 +bool TrueMQTT::Client::Impl::sendSubscribe(const std::string_view topic)
395 { 394 {
396 - LOG_TRACE(this, "Sending SUBSCRIBE packet for topic '" + topic + "'"); 395 + LOG_TRACE(this, "Sending SUBSCRIBE packet for topic '" + std::string(topic) + "'");
397 396
398 Packet packet(Packet::PacketType::SUBSCRIBE, 2); 397 Packet packet(Packet::PacketType::SUBSCRIBE, 2);
399 398
@@ -410,9 +409,9 @@ bool TrueMQTT::Client::Impl::sendSubscribe(const std::string &amp;topic) @@ -410,9 +409,9 @@ bool TrueMQTT::Client::Impl::sendSubscribe(const std::string &amp;topic)
410 return m_connection->send(std::move(packet)); 409 return m_connection->send(std::move(packet));
411 } 410 }
412 411
413 -bool TrueMQTT::Client::Impl::sendUnsubscribe(const std::string &topic) 412 +bool TrueMQTT::Client::Impl::sendUnsubscribe(const std::string_view topic)
414 { 413 {
415 - LOG_TRACE(this, "Sending unsubscribe message for topic '" + topic + "'"); 414 + LOG_TRACE(this, "Sending unsubscribe message for topic '" + std::string(topic) + "'");
416 415
417 Packet packet(Packet::PacketType::UNSUBSCRIBE, 2); 416 Packet packet(Packet::PacketType::UNSUBSCRIBE, 2);
418 417
src/Packet.h
@@ -66,10 +66,10 @@ public: @@ -66,10 +66,10 @@ public:
66 m_buffer.insert(m_buffer.end(), data, data + length); 66 m_buffer.insert(m_buffer.end(), data, data + length);
67 } 67 }
68 68
69 - void write_string(const std::string &str) 69 + void write_string(const std::string_view str)
70 { 70 {
71 write_uint16(static_cast<uint16_t>(str.size())); 71 write_uint16(static_cast<uint16_t>(str.size()));
72 - write(str.c_str(), str.size()); 72 + write(str.data(), str.size());
73 } 73 }
74 74
75 bool read_uint8(uint8_t &value) 75 bool read_uint8(uint8_t &value)
@@ -93,7 +93,7 @@ public: @@ -93,7 +93,7 @@ public:
93 return true; 93 return true;
94 } 94 }
95 95
96 - bool read_string(std::string &str) 96 + bool read_string(std::string_view &str)
97 { 97 {
98 uint16_t length; 98 uint16_t length;
99 if (!read_uint16(length)) 99 if (!read_uint16(length))
@@ -104,16 +104,15 @@ public: @@ -104,16 +104,15 @@ public:
104 { 104 {
105 return false; 105 return false;
106 } 106 }
107 - const char *data = reinterpret_cast<const char *>(m_buffer.data()) + m_read_offset;  
108 - str.assign(data, length); 107 +
  108 + str = std::string_view(reinterpret_cast<const char *>(m_buffer.data() + m_read_offset), length);
109 m_read_offset += length; 109 m_read_offset += length;
110 return true; 110 return true;
111 } 111 }
112 112
113 - void read_remaining(std::string &str) 113 + void read_remaining(std::string_view &str)
114 { 114 {
115 - const char *data = reinterpret_cast<const char *>(m_buffer.data()) + m_read_offset;  
116 - str.assign(data, m_buffer.size() - m_read_offset); 115 + str = std::string_view(reinterpret_cast<const char *>(m_buffer.data() + m_read_offset), m_buffer.size() - m_read_offset);
117 m_read_offset = m_buffer.size(); 116 m_read_offset = m_buffer.size();
118 } 117 }
119 118