Skip to content

Commit 48cf278

Browse files
authored
Implement Singleton Auxiliary class (#20)
* Implement Singleton Auxiliary class Signed-off-by: jparisu <javierparis@eprosima.com> * Implement tests Signed-off-by: jparisu <javierparis@eprosima.com> * Implement order related tests Signed-off-by: jparisu <javierparis@eprosima.com> * Add documentation Signed-off-by: jparisu <javierparis@eprosima.com> * fix windows issues Signed-off-by: jparisu <javierparis@eprosima.com> * apply suggestions Signed-off-by: jparisu <javierparis@eprosima.com> * uncrustify Signed-off-by: jparisu <javierparis@eprosima.com> Signed-off-by: jparisu <javierparis@eprosima.com>
1 parent 1967fc4 commit 48cf278

5 files changed

Lines changed: 593 additions & 0 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2022 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/**
16+
* @file Singleton.hpp
17+
*
18+
* This file contains class Singleton definition.
19+
*/
20+
21+
#pragma once
22+
23+
#include <memory>
24+
#include <mutex>
25+
26+
namespace eprosima {
27+
namespace utils {
28+
29+
/**
30+
* @brief This auxiliary class allows to easily create a Singleton class from a class that already exists.
31+
*
32+
* In order to create a Singleton of a class T, this class helps to define and implement the class T as a normal class
33+
* and then use it as a Singleton by using the type Singleton<T>.
34+
*
35+
* In order to create a Singleton class from T, it must have a default constructor and it is highly recommended
36+
* that the construction of the object is simple and could not fail.
37+
*
38+
* There could be more than one Singleton instance per class.
39+
* But because the static variables of this class, there could only be one Singleton per class.
40+
* For this purpose there is an \c Index that allows to create different Singleton instances of the same class
41+
* just by using a different index for each of them.
42+
*
43+
* @tparam T type of the value that will be converted to Singleton.
44+
* @tparam Index identifier of a specific Singleton element.
45+
*
46+
* @example
47+
* class SomethingDatabase; // A class that represents a database of things
48+
* using ProcessSharedDatabase = Singleton<SomethingDatabase>;
49+
*
50+
* // From now on, we can access an instance of Database shared with the whole process
51+
* ProcessSharedDatabase::get_instance()->do_something_in_database(args);
52+
*
53+
* @attention internal class in Singleton should have a protected ctor. Otherwise the static variable could be
54+
* copied and, or moved. User is responsible of creating a safe class.
55+
*
56+
* @attention this class is thread-safe but does not guarantee that internal class is thread safe neither protect
57+
* its methods and variables.
58+
*
59+
* @attention it is advised to not use directly Singleton<T> from code, but define previously a "class" that will
60+
* be the singleton by \c using and choosing a "random" \c Index so every user knows the name to access it.
61+
*/
62+
template <typename T, int Index = 0>
63+
class Singleton
64+
{
65+
public:
66+
67+
//! Get a reference to the instance of this Singleton
68+
static T* get_instance() noexcept;
69+
70+
/**
71+
* @brief Get a shared reference to the instance of this Singleton
72+
*
73+
* This method is useful to manage the order of destruction between singletons, as holding the shared_ptr
74+
* of one of them force it to not be destroyed until after the holder is destroyed.
75+
*
76+
* @warning Do not create a double loop between shared references in Singletons, or it will force a memory leak.
77+
*/
78+
static std::shared_ptr<T> get_shared_instance() noexcept;
79+
80+
private:
81+
82+
/**
83+
* @brief Protected default constructor specifies that none can create an instance of this class.
84+
*
85+
* @note this constructor must exist (cannot be deleted), otherwise this class could not be used.
86+
* However, this ctor will never be called anywhere.
87+
*/
88+
Singleton() = default;
89+
};
90+
91+
} /* namespace utils */
92+
} /* namespace eprosima */
93+
94+
// Include implementation template file
95+
#include <cpp_utils/types/impl/Singleton.ipp>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2022 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License")
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/**
16+
* @file Singleton.ipp
17+
*
18+
*/
19+
20+
#pragma once
21+
22+
namespace eprosima {
23+
namespace utils {
24+
25+
template <typename T, int Index>
26+
T* Singleton<T, Index>::get_instance() noexcept
27+
{
28+
return get_shared_instance().get();
29+
}
30+
31+
template <typename T, int Index>
32+
std::shared_ptr<T> Singleton<T, Index>::get_shared_instance() noexcept
33+
{
34+
static std::shared_ptr<T> instance_(new T());
35+
return instance_;
36+
}
37+
38+
} /* namespace utils */
39+
} /* namespace eprosima */

cpp_utils/test/unittest/types/CMakeLists.txt

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
############################
16+
# FUZZY TEST
17+
############################
18+
1519
set(TEST_NAME fuzzyTest)
1620

1721
set(TEST_SOURCES
@@ -39,3 +43,76 @@ add_unittest_executable(
3943
"${TEST_EXTRA_LIBRARIES}"
4044
"${TEST_NEEDED_SOURCES}"
4145
)
46+
47+
############################
48+
# SINGLETON TEST
49+
############################
50+
51+
set(TEST_NAME singletonTest)
52+
53+
set(TEST_SOURCES
54+
singletonTest.cpp
55+
${PROJECT_SOURCE_DIR}/src/cpp/wait/BooleanWaitHandler.cpp
56+
${PROJECT_SOURCE_DIR}/src/cpp/utils.cpp
57+
${PROJECT_SOURCE_DIR}/src/cpp/time/time_utils.cpp
58+
${PROJECT_SOURCE_DIR}/src/cpp/Formatter.cpp
59+
)
60+
61+
set(TEST_LIST
62+
trivial_get_instance
63+
different_index_class
64+
)
65+
66+
set(TEST_EXTRA_LIBRARIES
67+
fastcdr
68+
fastrtps
69+
$<$<BOOL:${WIN32}>:iphlpapi$<SEMICOLON>Shlwapi>
70+
)
71+
72+
set(TEST_NEEDED_SOURCES
73+
)
74+
75+
add_unittest_executable(
76+
"${TEST_NAME}"
77+
"${TEST_SOURCES}"
78+
"${TEST_LIST}"
79+
"${TEST_EXTRA_LIBRARIES}"
80+
"${TEST_NEEDED_SOURCES}"
81+
)
82+
83+
84+
############################
85+
# SINGLETON ORDER TEST
86+
############################
87+
88+
set(TEST_NAME singletonOrderTest)
89+
90+
set(TEST_SOURCES
91+
singletonOrderTest.cpp
92+
${PROJECT_SOURCE_DIR}/src/cpp/wait/BooleanWaitHandler.cpp
93+
${PROJECT_SOURCE_DIR}/src/cpp/utils.cpp
94+
${PROJECT_SOURCE_DIR}/src/cpp/time/time_utils.cpp
95+
${PROJECT_SOURCE_DIR}/src/cpp/Formatter.cpp
96+
)
97+
98+
set(TEST_LIST
99+
correct_construction_order
100+
correct_destruction_order
101+
)
102+
103+
set(TEST_EXTRA_LIBRARIES
104+
fastcdr
105+
fastrtps
106+
$<$<BOOL:${WIN32}>:iphlpapi$<SEMICOLON>Shlwapi>
107+
)
108+
109+
set(TEST_NEEDED_SOURCES
110+
)
111+
112+
add_unittest_executable(
113+
"${TEST_NAME}"
114+
"${TEST_SOURCES}"
115+
"${TEST_LIST}"
116+
"${TEST_EXTRA_LIBRARIES}"
117+
"${TEST_NEEDED_SOURCES}"
118+
)
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright 2022 Proyectos y Sistemas de Mantenimiento SL (eProsima).
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <thread>
16+
17+
#include <cpp_utils/testing/gtest_aux.hpp>
18+
#include <gtest/gtest.h>
19+
20+
#include <cpp_utils/types/Singleton.hpp>
21+
#include <cpp_utils/wait/BooleanWaitHandler.hpp>
22+
23+
/******************************
24+
* WARNING:
25+
* Theses tests are meant to run independently and in different processes.
26+
* As they use singletons it is required that singleton are constructed and destroyed within each test
27+
* to test it correctly.
28+
*/
29+
30+
using namespace eprosima::utils;
31+
32+
namespace test {
33+
34+
int value_to_check{0};
35+
36+
struct TestTypeOrder_LastIn;
37+
struct TestTypeOrder_FirstIn;
38+
struct TestTypeOrder_FirstIn_LastOut;
39+
struct TestTypeOrder_LastIn_FirstOut;
40+
41+
/*
42+
* These classes are meant to be used as singleton and check the order that they are created and destroyed.
43+
* They fail in ctor or dtor asserting if they are not created before or after others.
44+
*/
45+
46+
/**
47+
* This class is supposed to be created after TestTypeOrder_FirstIn_LastOut, so check the value in ctor and modify it.
48+
*/
49+
struct TestTypeOrder_LastIn
50+
{
51+
TestTypeOrder_LastIn()
52+
{
53+
check_correct();
54+
value_to_check = 200;
55+
}
56+
57+
void check_correct()
58+
{
59+
ASSERT_EQ(value_to_check, 100);
60+
}
61+
62+
};
63+
64+
/**
65+
* This class is supposed to be created before TestTypeOrder_LastIn.
66+
* It checks in construction that TestTypeOrder_LastIn still does not exist, and set value.
67+
*/
68+
struct TestTypeOrder_FirstIn
69+
{
70+
TestTypeOrder_FirstIn()
71+
{
72+
check_correct_ctor();
73+
value_to_check = 100;
74+
}
75+
76+
void check_correct_ctor()
77+
{
78+
ASSERT_EQ(value_to_check, 0);
79+
}
80+
81+
};
82+
83+
84+
/**
85+
* This class is supposed to be created before TestTypeOrder_LastIn_FirstOut.
86+
* It sets value when it starts and check that when leaving the value has already changed.
87+
*/
88+
struct TestTypeOrder_FirstIn_LastOut
89+
{
90+
TestTypeOrder_FirstIn_LastOut()
91+
{
92+
check_correct_ctor();
93+
value_to_check = 100;
94+
}
95+
96+
~TestTypeOrder_FirstIn_LastOut()
97+
{
98+
check_correct_dtor();
99+
}
100+
101+
void check_correct_ctor()
102+
{
103+
ASSERT_EQ(value_to_check, 0);
104+
}
105+
106+
void check_correct_dtor()
107+
{
108+
ASSERT_EQ(value_to_check, 200);
109+
}
110+
111+
};
112+
113+
/**
114+
* This class is supposed to be created after TestTypeOrder_LastIn or TestTypeOrder_LastIn_FirstOut.
115+
* Checks value in creation and set it again in destruction.
116+
*/
117+
struct TestTypeOrder_LastIn_FirstOut
118+
{
119+
TestTypeOrder_LastIn_FirstOut()
120+
: lock_reference_so_it_cannot_be_destroyed_before_this_(
121+
Singleton<TestTypeOrder_FirstIn_LastOut>::get_shared_instance())
122+
{
123+
check_correct();
124+
}
125+
126+
~TestTypeOrder_LastIn_FirstOut()
127+
{
128+
value_to_check = 200;
129+
}
130+
131+
void check_correct()
132+
{
133+
ASSERT_EQ(value_to_check, 100);
134+
}
135+
136+
std::shared_ptr<TestTypeOrder_FirstIn_LastOut> lock_reference_so_it_cannot_be_destroyed_before_this_;
137+
};
138+
139+
} /* namespace test */
140+
141+
TEST(singletonOrderTest, correct_construction_order)
142+
{
143+
// First call get_instance of the supposed to be created first
144+
Singleton<test::TestTypeOrder_FirstIn>::get_instance();
145+
146+
// call get_isntance of the one supposed to be constructed afterwards
147+
Singleton<test::TestTypeOrder_LastIn>::get_instance();
148+
149+
// Let singleton destroy by themselves
150+
}
151+
152+
TEST(singletonOrderTest, correct_destruction_order)
153+
{
154+
// First call get_instance of the supposed to be created first
155+
Singleton<test::TestTypeOrder_FirstIn_LastOut>::get_instance();
156+
157+
// call get_isntance of the one supposed to be constructed afterwards
158+
Singleton<test::TestTypeOrder_LastIn_FirstOut>::get_instance();
159+
160+
// Let singleton destroy by themselves and fail if they should be destroyed in different order
161+
}
162+
163+
int main(
164+
int argc,
165+
char** argv)
166+
{
167+
::testing::InitGoogleTest(&argc, argv);
168+
return RUN_ALL_TESTS();
169+
}

0 commit comments

Comments
 (0)