open62541pp 0.20.0
C++ wrapper of open62541
Loading...
Searching...
No Matches
contextmap.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <functional> // invoke
4#include <map>
5#include <memory>
6#include <mutex>
7#include <type_traits>
8#include <utility>
9
10namespace opcua::detail {
11
12/**
13 * Check if an object as a boolean `stale` flag.
14 *
15 * Context objects e.g. for MonitoredItems may have a lifetime and a callback might be called when
16 * the item is deleted. So the context objects should be able to delete itself.
17 * The context object passed to the callback as a parameter but the callback has no access to the
18 * container owning this context object. Each context object could carry a reference to its owning
19 * container and delete itself, but this can be problematic... Instead a simple flag `stale` can be
20 * set if the item is deleted and should be removed from its container, the `ContextMap`.
21 */
22template <typename T, typename = void>
23struct IsStaleable : std::false_type {};
24
25template <typename T>
26struct IsStaleable<T, std::void_t<decltype(std::declval<T>().stale)>>
27 : std::is_same<decltype(std::declval<T>().stale), bool> {};
28
29template <typename T>
30struct Staleable {
31 bool stale;
32 T item;
33};
34
35/**
36 * Thread-safe map for context objects.
37 * Context objects are reference as `void*` pointers in open62541 functions/callbacks. To prevent
38 * pointer-invalidation, the objects are stored as unique pointers.
39 * Stale objects will be removed when new objects are stored in the map.
40 */
41template <typename Key, typename Item>
42class ContextMap {
43public:
44 /// Access or insert specified element
45 Item* operator[](Key key) {
46 eraseStale();
47 auto lock = acquireLock();
48 auto& item = map_[key];
49 if (item == nullptr) {
50 item = std::make_unique<Item>(); // allocate item if empty
51 }
52 return item.get();
53 }
54
55 /// Inserts an element or assigns to the current element if the key already exists
56 Item* insert(Key key, std::unique_ptr<Item>&& item) {
57 eraseStale();
58 auto lock = acquireLock();
59 return map_.insert_or_assign(key, std::move(item)).first->second.get();
60 }
61
62 size_t erase(Key key) {
63 auto lock = acquireLock();
64 return map_.erase(key);
65 }
66
67 size_t eraseStale() {
68 const size_t count = map_.size();
69 if constexpr (IsStaleable<Item>::value) {
70 auto lock = acquireLock();
71 for (auto it = map_.begin(); it != map_.end();) {
72 if (it->second->stale) {
73 it = map_.erase(it);
74 } else {
75 ++it;
76 }
77 }
78 }
79 return count - map_.size();
80 }
81
82 bool contains(Key key) const {
83 auto lock = acquireLock();
84 return map_.count(key) > 0;
85 }
86
87 const Item* find(Key key) const {
88 auto lock = acquireLock();
89 auto it = map_.find(key);
90 if (it != map_.end()) {
91 return it->second.get();
92 }
93 return nullptr;
94 }
95
96 template <typename F>
97 void iterate(F&& func) const { // NOLINT(cppcoreguidelines-missing-std-forward)
98 auto lock = acquireLock();
99 for (const auto& pair : map_) {
100 std::invoke(func, pair);
101 }
102 }
103
104private:
105 [[nodiscard]] auto acquireLock() const {
106 return std::scoped_lock(mutex_);
107 }
108
109 std::map<Key, std::unique_ptr<Item>> map_;
110 mutable std::mutex mutex_;
111};
112
113} // namespace opcua::detail