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