Skip to content

Commit 0fbd8ba

Browse files
authored
Merge pull request #377 from sankurm/locked_ptr_const_nodiscard_fns
const overloads & nodiscard for LockedPtr members
2 parents 1a88b75 + 781a694 commit 0fbd8ba

3 files changed

Lines changed: 220 additions & 47 deletions

File tree

score/concurrency/locked_ptr.h

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,16 +116,44 @@ class LockedPtr
116116
* @brief Dereference operator to access the underlying object.
117117
* @return Reference to the underlying object.
118118
*/
119-
T& operator*()
119+
[[nodiscard]] T& operator*()
120120
{
121121
return *ptr_;
122122
}
123123

124+
/**
125+
* @brief Dereference operator to access the underlying object.
126+
* @return Reference to the underlying object.
127+
*/
128+
[[nodiscard]] const T& operator*() const
129+
{
130+
return *ptr_;
131+
}
132+
133+
/**
134+
* @brief Pointer access to the underlying object.
135+
* @return Pointer to the underlying object.
136+
*/
137+
[[nodiscard]] T* operator->()
138+
{
139+
return ptr_;
140+
}
141+
124142
/**
125143
* @brief Pointer access to the underlying object.
126144
* @return Pointer to the underlying object.
127145
*/
128-
T* operator->()
146+
[[nodiscard]] const T* operator->() const
147+
{
148+
return ptr_;
149+
}
150+
151+
/**
152+
* @brief Gets the raw pointer to the underlying object.
153+
* @return Pointer to the underlying object.
154+
* @remarks This does not transfer ownership; the LockedPtr still manages the lock.
155+
*/
156+
[[nodiscard]] T* get() noexcept
129157
{
130158
return ptr_;
131159
}
@@ -135,7 +163,7 @@ class LockedPtr
135163
* @return Pointer to the underlying object.
136164
* @remarks This does not transfer ownership; the LockedPtr still manages the lock.
137165
*/
138-
[[nodiscard]] T* get() const noexcept
166+
[[nodiscard]] const T* get() const noexcept
139167
{
140168
return ptr_;
141169
}
@@ -152,6 +180,18 @@ class LockedPtr
152180
return UnlockGuard<Lock>{lock_};
153181
}
154182

183+
/**
184+
* @brief Creates an UnlockGuard that temporarily unlocks the Lock.
185+
* @return An UnlockGuard that unlocks the Lock for its lifetime.
186+
* @remarks It is Undefined Behaviour if the returned UnlockGuard outlives the LockedPtr.
187+
* The UnlockGuard should be destroyed before the LockedPtr is destroyed.
188+
* Moving the LockedPtr while an UnlockGuard exists is also Undefined Behaviour.
189+
*/
190+
[[nodiscard]] UnlockGuard<Lock> unlock_guard() const
191+
{
192+
return UnlockGuard<Lock>{lock_};
193+
}
194+
155195
/**
156196
* @brief Bool conversion operator checks if the LockedPtr is managing a valid pointer.
157197
* @return true if the LockedPtr is managing a non-null pointer, false otherwise.
@@ -230,7 +270,7 @@ class LockedPtr
230270

231271
private:
232272
T* ptr_;
233-
Lock lock_;
273+
mutable Lock lock_;
234274
};
235275

236276
} // namespace concurrency

score/concurrency/locked_ptr_test.cpp

Lines changed: 156 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,15 @@ namespace test
3232

3333
namespace
3434
{
35+
template <typename Lock>
36+
using LPtr2int = LockedPtr<int, Lock>;
37+
3538
struct IntWrapper
3639
{
3740
int value;
3841
};
42+
43+
using LPtr2IntW = LockedPtr<IntWrapper, std::unique_lock<MockMutex>>;
3944
} // namespace
4045

4146
TEST(LockedPtrTest, ConstructionWithTypes)
@@ -72,53 +77,161 @@ TEST(LockedPtrTest, SwappingWithTypes)
7277
<< "LockedPtr with shared_lock should be swappable";
7378
}
7479

75-
TEST(LockedPtrTest, TFunctionsWithTypes)
80+
TEST(LockedPtrTest, DereferenceTests)
7681
{
77-
// Functions working with T
78-
EXPECT_TRUE((std::is_same_v<std::invoke_result_t<decltype(&LockedPtr<int, BasicLockableArchetype>::operator*),
79-
LockedPtr<int, BasicLockableArchetype>*>,
80-
int&>))
81-
<< "LockedPtr::operator*() should return int&";
82-
EXPECT_TRUE((std::is_same_v<std::invoke_result_t<decltype(&LockedPtr<const int, BasicLockableArchetype>::operator*),
83-
LockedPtr<const int, BasicLockableArchetype>*>,
84-
const int&>))
85-
<< "LockedPtr::operator*() should return const int&";
86-
EXPECT_TRUE((std::is_same_v<std::invoke_result_t<decltype(&LockedPtr<int, BasicLockableArchetype>::operator->),
87-
LockedPtr<int, BasicLockableArchetype>*>,
88-
int*>))
89-
<< "LockedPtr::operator->() should return int*";
90-
EXPECT_TRUE(
91-
(std::is_same_v<std::invoke_result_t<decltype(&LockedPtr<const int, BasicLockableArchetype>::operator->),
92-
LockedPtr<const int, BasicLockableArchetype>*>,
93-
const int*>))
94-
<< "LockedPtr::operator->() should return const int*";
95-
EXPECT_TRUE((std::is_same_v<std::invoke_result_t<decltype(&LockedPtr<int, BasicLockableArchetype>::get),
96-
LockedPtr<int, BasicLockableArchetype>*>,
97-
int*>))
98-
<< "LockedPtr::get() should return int*";
99-
EXPECT_TRUE((std::is_same_v<std::invoke_result_t<decltype(&LockedPtr<const int, BasicLockableArchetype>::get),
100-
LockedPtr<const int, BasicLockableArchetype>*>,
101-
const int*>))
102-
<< "LockedPtr::get() should return const int*";
82+
int x = 10;
83+
LPtr2int<BasicLockableArchetype> lp_basic{&x, BasicLockableArchetype{}};
84+
85+
EXPECT_EQ(*lp_basic, 10);
86+
EXPECT_EQ(*std::as_const(lp_basic), 10);
87+
88+
*lp_basic = 20;
89+
EXPECT_EQ(*lp_basic, 20);
90+
EXPECT_EQ(*std::as_const(lp_basic), 20);
91+
92+
decltype(auto) x_ref = *lp_basic;
93+
EXPECT_TRUE((std::is_same_v<decltype(x_ref), int&>)) << "LockedPtr::operator* should return int&";
94+
95+
decltype(auto) cx_ref = *std::as_const(lp_basic);
96+
EXPECT_TRUE((std::is_same_v<decltype(cx_ref), const int&>)) << "LockedPtr::operator* should return const int&";
10397
}
10498

105-
TEST(LockedPtrTest, LockFunctionsWithTypes)
99+
TEST(LockedPtrTest, PtrUsageTests)
106100
{
107-
// Functions working with Lock
108-
EXPECT_TRUE((std::is_same_v<std::invoke_result_t<decltype(&LockedPtr<int, BasicLockableArchetype>::unlock_guard),
109-
LockedPtr<int, BasicLockableArchetype>*>,
110-
UnlockGuard<BasicLockableArchetype>>))
111-
<< "LockedPtr::unlock_guard() should return UnlockGuard<BasicLockableArchetype>";
112-
EXPECT_TRUE(
113-
(std::is_same_v<std::invoke_result_t<decltype(&LockedPtr<int, std::unique_lock<std::mutex>>::unlock_guard),
114-
LockedPtr<int, std::unique_lock<std::mutex>>*>,
115-
UnlockGuard<std::unique_lock<std::mutex>>>))
116-
<< "LockedPtr::unlock_guard() should return UnlockGuard<unique_lock>";
117-
EXPECT_TRUE((std::is_same_v<
118-
std::invoke_result_t<decltype(&LockedPtr<int, std::shared_lock<std::shared_mutex>>::unlock_guard),
119-
LockedPtr<int, std::shared_lock<std::shared_mutex>>*>,
120-
UnlockGuard<std::shared_lock<std::shared_mutex>>>))
121-
<< "LockedPtr::unlock_guard() should return UnlockGuard<shared_lock>";
101+
IntWrapper obj{42};
102+
MockMutex mut{};
103+
104+
LPtr2IntW lp_obj{&obj, std::unique_lock<MockMutex>{mut}};
105+
EXPECT_EQ(lp_obj->value, 42);
106+
EXPECT_EQ(std::as_const(lp_obj)->value, 42);
107+
108+
lp_obj->value = 100;
109+
EXPECT_EQ(lp_obj->value, 100);
110+
EXPECT_EQ(std::as_const(lp_obj)->value, 100);
111+
}
112+
113+
TEST(LockedPtrTest, GetTests)
114+
{
115+
IntWrapper obj{42};
116+
MockMutex mut{};
117+
118+
LPtr2IntW lp_obj{&obj, std::unique_lock<MockMutex>{mut}};
119+
EXPECT_EQ(lp_obj.get(), &obj);
120+
EXPECT_EQ(std::as_const(lp_obj).get(), &obj);
121+
122+
decltype(auto) x_ptr = lp_obj.get();
123+
EXPECT_TRUE((std::is_same_v<decltype(x_ptr), IntWrapper*>)) << "LockedPtr::get() should return IntWrapper*";
124+
125+
decltype(auto) cx_ptr = std::as_const(lp_obj).get();
126+
EXPECT_TRUE((std::is_same_v<decltype(cx_ptr), const IntWrapper*>))
127+
<< "LockedPtr::get() should return const IntWrapper*";
128+
}
129+
130+
TEST(LockedPtrTest, UnlockGuardBasicLockableArchetypeTests)
131+
{
132+
int x = 42;
133+
134+
LPtr2int<BasicLockableArchetype> lp_basic{&x, BasicLockableArchetype{}};
135+
136+
{
137+
auto ug_basic = lp_basic.unlock_guard();
138+
EXPECT_TRUE((std::is_same_v<decltype(ug_basic), UnlockGuard<BasicLockableArchetype>>))
139+
<< "unlock_guard() should return UnlockGuard<BasicLockableArchetype>";
140+
}
141+
142+
{
143+
auto cug_basic = std::as_const(lp_basic).unlock_guard();
144+
EXPECT_TRUE((std::is_same_v<decltype(cug_basic), UnlockGuard<BasicLockableArchetype>>))
145+
<< "unlock_guard() should return UnlockGuard<BasicLockableArchetype>";
146+
}
147+
}
148+
149+
TEST(LockedPtrTest, UnlockGuardUniqueLockTests)
150+
{
151+
int x = 42;
152+
153+
std::mutex mut{};
154+
LPtr2int<std::unique_lock<std::mutex>> lp_mut{&x, std::unique_lock{mut}};
155+
156+
{
157+
auto ug_mut = lp_mut.unlock_guard();
158+
EXPECT_TRUE((std::is_same_v<decltype(ug_mut), UnlockGuard<std::unique_lock<std::mutex>>>))
159+
<< "unlock_guard() should return UnlockGuard<std::unique_lock<std::mutex>>";
160+
}
161+
162+
{
163+
auto cug_mut = std::as_const(lp_mut).unlock_guard();
164+
EXPECT_TRUE((std::is_same_v<decltype(cug_mut), UnlockGuard<std::unique_lock<std::mutex>>>))
165+
<< "unlock_guard() should return UnlockGuard<std::unique_lock<std::mutex>>";
166+
}
167+
168+
{
169+
IntWrapper obj{42};
170+
MockMutex mut{};
171+
LPtr2IntW lp_obj{&obj, std::unique_lock<MockMutex>{mut}};
172+
EXPECT_TRUE(mut.is_locked());
173+
{
174+
auto ug = lp_obj.unlock_guard();
175+
EXPECT_FALSE(mut.is_locked());
176+
}
177+
EXPECT_TRUE(mut.is_locked());
178+
}
179+
180+
{
181+
IntWrapper obj{42};
182+
MockMutex mut{};
183+
LPtr2IntW lp_obj{&obj, std::unique_lock<MockMutex>{mut}};
184+
EXPECT_TRUE(mut.is_locked());
185+
{
186+
auto cug = std::as_const(lp_obj).unlock_guard();
187+
EXPECT_FALSE(mut.is_locked());
188+
}
189+
EXPECT_TRUE(mut.is_locked());
190+
}
191+
}
192+
193+
TEST(LockedPtrTest, UnlockGuardSharedLockTests)
194+
{
195+
int x = 42;
196+
197+
std::shared_mutex sh_mut{};
198+
LPtr2int<std::shared_lock<std::shared_mutex>> lp_sh{&x, std::shared_lock{sh_mut}};
199+
200+
{
201+
auto ug_sh = lp_sh.unlock_guard();
202+
EXPECT_TRUE((std::is_same_v<decltype(ug_sh), UnlockGuard<std::shared_lock<std::shared_mutex>>>))
203+
<< "unlock_guard() should return UnlockGuard<std::shared_lock<std::shared_mutex>>";
204+
}
205+
206+
{
207+
auto cug_sh = std::as_const(lp_sh).unlock_guard();
208+
EXPECT_TRUE((std::is_same_v<decltype(cug_sh), UnlockGuard<std::shared_lock<std::shared_mutex>>>))
209+
<< "unlock_guard() should return UnlockGuard<std::shared_lock<std::shared_mutex>>";
210+
}
211+
212+
{
213+
IntWrapper obj{42};
214+
MockSharedMutex mut{};
215+
LockedPtr lp_obj{&obj, std::shared_lock<MockSharedMutex>{mut}};
216+
EXPECT_TRUE(mut.is_shared_locked());
217+
{
218+
auto ug = lp_obj.unlock_guard();
219+
EXPECT_FALSE(mut.is_shared_locked());
220+
}
221+
EXPECT_TRUE(mut.is_shared_locked());
222+
}
223+
224+
{
225+
IntWrapper obj{42};
226+
MockSharedMutex mut{};
227+
LockedPtr lp_obj{&obj, std::shared_lock<MockSharedMutex>{mut}};
228+
EXPECT_TRUE(mut.is_shared_locked());
229+
{
230+
auto cug = std::as_const(lp_obj).unlock_guard();
231+
EXPECT_FALSE(mut.is_shared_locked());
232+
}
233+
EXPECT_TRUE(mut.is_shared_locked());
234+
}
122235
}
123236

124237
TEST(LockedPtrTest, NonConstWithUniqueLock)

score/concurrency/test_types.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,26 @@ class MockMutex
6464
bool locked_{false};
6565
};
6666

67+
class MockSharedMutex : public MockMutex
68+
{
69+
public:
70+
void lock_shared()
71+
{
72+
shared_locked_ = true;
73+
}
74+
void unlock_shared()
75+
{
76+
shared_locked_ = false;
77+
}
78+
bool is_shared_locked() const
79+
{
80+
return shared_locked_;
81+
}
82+
83+
private:
84+
bool shared_locked_{false};
85+
};
86+
6787
class MockMutexParameterized
6888
{
6989
public:

0 commit comments

Comments
 (0)