Skip to content

Commit 73cb7b1

Browse files
committed
Updated and clarified the documentation
Thread safety guarantees have been documented more precisely.
1 parent 9506d14 commit 73cb7b1

4 files changed

Lines changed: 76 additions & 57 deletions

File tree

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ if (UNIX)
1818
target_link_libraries(singleton INTERFACE Threads::Threads)
1919
endif()
2020

21+
# Documentation generation using Doxygen
22+
find_package(Doxygen)
23+
if (DOXYGEN_FOUND)
24+
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "README.md")
25+
doxygen_add_docs(doc "README.md" include)
26+
endif()
27+
2128
# Unit Test.
2229
if (NOT SINGLETON_SKIP_TEST)
2330
enable_testing()

README.md

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,14 @@ For more information about its usage, see the documentation within the [include/
7272

7373
# Testing
7474

75-
The singleton has private methods which are designed for testing. These methods are normally inaccessible for production code, which is desired for software stability and quality.
75+
The singleton has an interface designed for testing only. These interfaces should not be used in production code, because the software stability and quality cannot be guaranteed.
7676

77-
The C++ standard states that during explicit template instantiations accessibility checking is not performed. It is possible to exploit this to gain access to private members. Why should we want to get around the accessibility limitation of private members? Because unit tests often need to be able to introspect objects. In the case of this library, the unsafe testing functionality is only accessible by exploiting this behaviour.
78-
79-
While it is complicated and impractical to write the template code to access private members - which is a good thing - there are multiple libraries designed to gain access to this functionality for unit tests. The following example shows the usage of the `Reset()` and `Inject()` methods through the [access_private library](https://github.com/martong/access_private/) by [Gábor Márton](https://github.com/martong).
77+
To access the testing interfaces, test code can include the `singleton_test.hpp` file. This provides access for the tests to use the `::testing::SingletonTestApi` class.
78+
The following example shows the usage of the `::testing::SingletonTestApi<T>::Reconstruct()` and `::testing::SingletonTestApi<T>::Inject()` methods.
8079

8180
```cpp
8281
#include <singleton.hpp>
83-
#include <access_private.hpp>
82+
#include <singleton_test.hpp>
8483

8584
class MySingleton : public Singleton<MySingleton>
8685
{
@@ -93,8 +92,6 @@ protected:
9392

9493
virtual int MyFunction();
9594
};
96-
ACCESS_PRIVATE_STATIC_FUN(MySingleton, MySingleton&(), Reset);
97-
ACCESS_PRIVATE_STATIC_FUN(MySingleton, void(MySingleton*), Inject);
9895

9996
int main()
10097
{
@@ -105,7 +102,7 @@ int main()
105102
auto realResult1 = instance.MyFunction();
106103

107104
// This reconstructs the singleton instance and returns the same instance as earlier.
108-
auto& instance2 = call_private_static::MySingleton::Reset();
105+
auto& instance2 = SingletonTestApi<MySingleton>::Reconstruct()
109106

110107
// This returns the real implementation's result from the fresh instance.
111108
auto realResult2 = instance.MyFunction();
@@ -119,7 +116,7 @@ int main()
119116

120117
// This injects the mock implementation.
121118
// The real instance is destroyed, the `instance` and `instance2` variables are invalidated.
122-
call_private_static::MySingleton::Inject(&mock);
119+
SingletonTestApi<MySingleton>::Inject(&mock);
123120

124121
// This returns the mock implementation.
125122
auto& instance3 = MySingleton::Get();
@@ -128,20 +125,10 @@ int main()
128125
}
129126
```
130127
131-
> [!TIP]
132-
> When the Singleton has a non-trivial constructor, and the `Reset` function is accessed, then each parameter type must be an r-value reference. For example if `MySingleton`'s constructor is:
133-
>
134-
> ```.cpp
135-
> MySingleton::MySingleton(FooType arg1, BarType&& arg2);
136-
> ```
137-
>
138-
> Then the `Reset` function's signature is:
139-
>
140-
> ```.cpp
141-
> MySingleton& MySingleton::Reset(FooType&&, BarType&&);
142-
> ```
143-
144128
> [!NOTE]
145129
> To be able to properly use the `Inject` function, the production code should not cache a reference or pointer to the returned instance. Otherwise the injected mock doesn't take effect and the real instance is used, which is destroyed. This may cause a crash or an other memory corruption style issue.
146130
147-
For more examples and "requirements", it is recommended to view this library's [test code](blob/main/test/unit/singleton_test.cpp).
131+
For more examples and behavioral requirements, it is recommended to view this library's [test code](blob/main/test/unit/singleton_test.cpp).
132+
133+
> [!TIP]
134+
> Earlier versions of this library used the [access_private library](https://github.com/martong/access_private/) by [Gábor Márton](https://github.com/martong) to access the Singleton's testing-only methods through explicit template instantiation's disabled accessibility check. This method is still supported, but it is untested. In version 2.0, the `Reset` method's signature is changed to use universal references, so `&&` must be added after all parameters.

include/singleton.hpp

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,23 @@
1010

1111
namespace testing
1212
{
13-
// Forward declaration of the testing interface.
13+
// Forward declaration of the testing interface.
1414
template <typename T>
1515
struct SingletonTestApi;
1616
}
1717

1818
/// Implements the singleton pattern, but makes unit testing easy.
19-
/** To use the Singleton class normally, inherit from it with the CRTP style, like:
19+
/** To use the Singleton class normally, inherit from it with the [CRTP] style, like:
2020
*
2121
* ```cpp
2222
* class MyClass : public Singleton<MyClass> { impl... };
2323
* ```
2424
*
2525
* After this, external code is able to get the `MyClass` instance by using `MyClass::Get()`.
2626
*
27-
* To test your singleton, an access-private library implementation is required, such as
28-
* <https://github.com/martong/access_private>. Using the access-private library, invoke the
29-
* `MyClass::Inject()` function to inject a mock implementation into the singleton. It is also
30-
* possible to reconstruct the Singleton with different constructor arguments by using the
31-
* `MyClass::Reset()` function.
32-
*
33-
* @remark The `Inject()` and `Reset()` functions are NOT thread safe! They should only be used
34-
* in test code sections while implementation code is not running on a different thread.
27+
* To test your singleton, use `::testing::SingletonTestApi<T>` from `singleton_test.hpp`.
28+
*
29+
* [CRTP]: https://en.cppreference.com/w/cpp/language/crtp.html
3530
*/
3631
template <typename T>
3732
struct Singleton
@@ -42,7 +37,7 @@ struct Singleton
4237
/// Returns the instance of the class.
4338
/** @remark The constructor arguments are only used if the instance is not constructed yet.
4439
On subsequent calls, get may be called without arguments. Use `TryGet()` instead to
45-
check if the instance is already constructed.
40+
check if the instance is already constructed.
4641
*/
4742
template <typename ...Args>
4843
static T& Get(Args&&... args)
@@ -59,6 +54,7 @@ struct Singleton
5954
*/
6055
static T* TryGet() noexcept
6156
{
57+
// NOTE: Thread safety here assumes that pointers can be read/written atomically.
6258
return g_instance;
6359
}
6460

@@ -73,15 +69,19 @@ struct Singleton
7369
{
7470
Instance() noexcept = default;
7571
Instance(const Instance&) = delete;
76-
~Instance()
72+
~Instance()
7773
{
7874
// Destroys the locally-initialized instance. Injected ones are ignored (no ownership).
7975
if (m_pExtern == LOCAL_INSTANCE_ID)
8076
GetBuffer().~T();
8177
}
8278
Instance& operator =(const Instance&) = delete;
8379
/// Returns the current instance.
84-
operator T* () noexcept { return m_pExtern == LOCAL_INSTANCE_ID ? &GetBuffer() : m_pExtern; }
80+
operator T* () noexcept
81+
{
82+
// m_pExtern is used only by test code, so thread safety is not a concern here.
83+
return m_pExtern == LOCAL_INSTANCE_ID ? &GetBuffer() : m_pExtern;
84+
}
8585
/// Constructs the singleton within the local buffer.
8686
template <typename ...Args>
8787
void Emplace(Args&&... args)
@@ -96,7 +96,7 @@ struct Singleton
9696
{
9797
m_pExtern = nullptr;
9898
throw;
99-
}
99+
}
100100
}
101101
/// Sets an external object as the instance.
102102
/** @remark If `ptr` is `nullptr`, this is just reset.
@@ -149,7 +149,7 @@ struct Singleton
149149
union U { std::once_flag asOnceFlag; U(){} ~U(){} } buffer;
150150
} g_onceFlag;
151151

152-
/// Internal function, use the `::testing::SingletonTestApi<T>::Reconstruct()` function instead.
152+
/// Internal function, use the `::testing::SingletonTestApi<T>::Reconstruct()` function instead.
153153
template <typename ...Args>
154154
static T& Reset(Args&&... args)
155155
{
@@ -158,7 +158,7 @@ struct Singleton
158158
}
159159

160160
/// Internal function, use the `::testing::SingletonTestApi<T>::Inject()` function instead.
161-
static void Inject(T* object)
161+
static void Inject(T* object)
162162
{
163163
if (object)
164164
std::call_once(g_onceFlag, []() {});
@@ -176,7 +176,7 @@ struct Singleton
176176
g_instance.Emplace(std::forward<Args>(args)...);
177177
}
178178

179-
// The testing interface can access testing-only functions.
179+
// The testing interface can access testing-only functions.
180180
friend struct testing::SingletonTestApi<T>;
181181
};
182182
template <typename T>

include/singleton_test.hpp

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,62 +4,87 @@
44
/** @file
55
* @brief Provides testing functions for Singleton<T>.
66
*
7-
* This file should only be included in unit tests. The unit test code should be added to the
8-
* `::testing` namespace, from which the provided interfaces are directly usable.
7+
* @warning DO NOT USE THIS FILE IN PRODUCTION CODE!
8+
* This interface bypasses the singleton's encapsulation and exposes functions intended for
9+
* unit testing only.
10+
*
11+
* The unit test code should be added to the `::testing` namespace, from which the provided
12+
* interfaces are directly usable.
913
*/
1014

1115
#include "singleton.hpp"
1216

1317
namespace testing
1418
{
1519
/// Provides access to Singleton's testing functions.
16-
/** @warning DO NOT USE THIS IN PRODUCTION CODE
17-
* This interface bypasses the singleton's encapsulation and exposes functions intended for unit
18-
* testing only.
20+
/** @warning DO NOT USE THIS IN PRODUCTION CODE!
21+
* This interface bypasses the singleton's encapsulation and exposes functions intended for
22+
* unit testing only.
23+
*
24+
* Invoke the `Inject()` method to inject a mock implementation into the singleton. It is also
25+
* possible to reconstruct the Singleton with different constructor arguments by using the
26+
* `Reconstruct()` method.
1927
*
2028
* Usage:
2129
*
2230
* ```cpp
23-
* testing::SingletonTestApi<MySingletonType>::Reset(constructorArg1, constructorArg2);
31+
* testing::SingletonTestApi<MySingletonType>::Reconstruct(constructorArg1, constructorArg2);
2432
* testing::SingletonTestApi<MySingletonType>::Inject(&myMockInstance);
2533
* ```
34+
*
35+
* @remark This class's methods are NOT thread safe! They should only be used in test code
36+
* sections while implementation code is not running on a different thread.
2637
*/
2738
template <typename T>
2839
struct SingletonTestApi
2940
{
3041
/// (Re)constructs the internal singleton instance.
31-
/** If an existing singleton instance was already constructed, it is destroyed. If an external
32-
* instance was injected, it is overridden with the newly constructed instance.
33-
*
34-
* @remark This function is not thread safe. It is intended for tests, not production code.
42+
/** If an existing singleton instance was already constructed, it is destroyed. If an
43+
* external instance was injected, it is overridden with the newly constructed internal
44+
* instance.
45+
* @remark This function is not thread safe. It is intended for tests, not production
46+
* code. Do not call this function twice, or any of the same singleton's getter
47+
* functions at the same time.
3548
*/
3649
template <typename ...Args>
3750
static T& Reconstruct(Args&&... args)
3851
{
3952
return SingletonType::Reset(std::forward<Args>(args)...);
4053
}
4154

42-
/// @overload
55+
/// This is an alias for `Reconstruct()`.
56+
/** It is recommended to use `Reconstruct()`, because it is more descriptive.
57+
*/
4358
template <typename ...Args>
4459
static T& Reset(Args&&... args)
4560
{
4661
return SingletonType::Reset(std::forward<Args>(args)...);
4762
}
4863

4964
/// Injects an external instance into the singleton.
50-
/** If an external instance is injected, the `Get()` function
51-
* @param object The object is taken without ownership and must be deleted by the caller.
52-
* @remark If `object` is `nullptr`, it resets the singleton to uninitialized state, and the
65+
/** The injected class instance is returned on the upcoming calls to the `Singleton<T>::Get()`
66+
* function.
67+
*
68+
* If an existing singleton instance was already constructed, it is destroyed.
69+
*
70+
* If `object` is `nullptr`, it resets the singleton to uninitialized state, and the
5371
* next invocation of `Get()` reconstructs the instance.
72+
* @param object The object is taken without ownership and must be deleted by the caller.
73+
* @remark This function is not thread safe. It is intended for tests, not production
74+
* code. Do not call this function twice, or any of the same singleton's getter
75+
* functions at the same time.
5476
*/
5577
static void Inject(T* object)
5678
{
5779
SingletonType::Inject(object);
5880
}
5981

60-
/// Resets the singleton to uninitialized state.
61-
/** @remark This is an alias for `Inject(nullptr)`.
62-
*/
82+
/// Resets the singleton to uninitialized state.
83+
/** This is an alias for `Inject()` with a `nullptr` argument.
84+
* @remark This function is not thread safe. It is intended for tests, not production
85+
* code. Do not call this function twice, or any of the same singleton's getter
86+
* functions at the same time.
87+
*/
6388
static void Clear()
6489
{
6590
SingletonType::Inject(nullptr);

0 commit comments

Comments
 (0)