-
Notifications
You must be signed in to change notification settings - Fork 8
Implement Singleton Auxiliary class #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| // Copyright 2022 Proyectos y Sistemas de Mantenimiento SL (eProsima). | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| /** | ||
| * @file Singleton.hpp | ||
| * | ||
| * This file contains class Singleton definition. | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <memory> | ||
| #include <mutex> | ||
|
|
||
| namespace eprosima { | ||
| namespace utils { | ||
|
|
||
| /** | ||
| * @brief This auxiliary class allows to easily create a Singleton class from a class that already exists. | ||
| * | ||
| * In order to create a Singleton of a class T, this class helps to define and implement the class T as a normal class | ||
| * and then use it as a Singleton by using the type Singleton<T>. | ||
| * | ||
| * In order to create a Singleton class from T, it must have a default constructor and it is highly recommended | ||
| * that the construction of the object is simple and could not fail. | ||
| * | ||
| * There could be more than one Singleton instance per class. | ||
| * But because the static variables of this class, there could only be one Singleton per class. | ||
| * For this purpose there is an \c Index that allows to create different Singleton instances of the same class | ||
| * just by using a different index for each of them. | ||
| * | ||
| * @tparam T type of the value that will be converted to Singleton. | ||
| * @tparam Index identifier of a specific Singleton element. | ||
| * | ||
| * @example | ||
| * class SomethingDatabase; // A class that represents a database of things | ||
| * using ProcessSharedDatabase = Singleton<SomethingDatabase>; | ||
| * | ||
| * // From now on, we can access an instance of Database shared with the whole process | ||
| * ProcessSharedDatabase::get_instance()->do_something_in_database(args); | ||
| * | ||
| * @attention internal class in Singleton should have a protected ctor. Otherwise the static variable could be | ||
| * copied and, or moved. User is responsible of creating a safe class. | ||
| * | ||
| * @attention this class is thread-safe but does not guarantee that internal class is thread safe neither protect | ||
| * its methods and variables. | ||
| * | ||
| * @attention it is advised to not use directly Singleton<T> from code, but define previously a "class" that will | ||
| * be the singleton by \c using and choosing a "random" \c Index so every user knows the name to access it. | ||
| */ | ||
| template <typename T, int Index = 0> | ||
| class Singleton | ||
| { | ||
| public: | ||
|
|
||
| //! Get a reference to the instance of this Singleton | ||
| static T* get_instance() noexcept; | ||
|
|
||
| /** | ||
| * @brief Get a shared reference to the instance of this Singleton | ||
| * | ||
| * This method is useful to manage the order of destruction between singletons, as holding the shared_ptr | ||
| * of one of them force it to not be destroyed until after the holder is destroyed. | ||
| * | ||
| * @warning Do not create a double loop between shared references in Singletons, or it will force a memory leak. | ||
| */ | ||
| static std::shared_ptr<T> get_shared_instance() noexcept; | ||
|
|
||
| private: | ||
|
|
||
| /** | ||
| * @brief Protected default constructor specifies that none can create an instance of this class. | ||
| * | ||
| * @note this constructor must exist (cannot be deleted), otherwise this class could not be used. | ||
| * However, this ctor will never be called anywhere. | ||
| */ | ||
| Singleton() = default; | ||
| }; | ||
|
|
||
| } /* namespace utils */ | ||
| } /* namespace eprosima */ | ||
|
|
||
| // Include implementation template file | ||
| #include <cpp_utils/types/impl/Singleton.ipp> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // Copyright 2022 Proyectos y Sistemas de Mantenimiento SL (eProsima). | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License") | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| /** | ||
| * @file Singleton.ipp | ||
| * | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| namespace eprosima { | ||
| namespace utils { | ||
|
|
||
| template <typename T, int Index> | ||
| T* Singleton<T, Index>::get_instance() noexcept | ||
| { | ||
| return get_shared_instance().get(); | ||
| } | ||
|
|
||
| template <typename T, int Index> | ||
| std::shared_ptr<T> Singleton<T, Index>::get_shared_instance() noexcept | ||
| { | ||
| static std::shared_ptr<T> instance_(new T()); | ||
|
juanlofer-eprosima marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to leave here our offline conversation: Getting a pointer instead of a reference to an object in stack might be more dangerous (could be deleted by user), we need to have this in mind when using it and modify it if needed.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As we discussed, this way allows to control the liveliness of the internal object by shared_ptr, while having it in stack would not. |
||
| return instance_; | ||
| } | ||
|
|
||
| } /* namespace utils */ | ||
| } /* namespace eprosima */ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| // Copyright 2022 Proyectos y Sistemas de Mantenimiento SL (eProsima). | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| #include <thread> | ||
|
|
||
| #include <cpp_utils/testing/gtest_aux.hpp> | ||
| #include <gtest/gtest.h> | ||
|
|
||
| #include <cpp_utils/types/Singleton.hpp> | ||
| #include <cpp_utils/wait/BooleanWaitHandler.hpp> | ||
|
|
||
| /****************************** | ||
| * WARNING: | ||
| * Theses tests are meant to run independently and in different processes. | ||
| * As they use singletons it is required that singleton are constructed and destroyed within each test | ||
| * to test it correctly. | ||
| */ | ||
|
|
||
| using namespace eprosima::utils; | ||
|
|
||
| namespace test { | ||
|
|
||
| int value_to_check{0}; | ||
|
|
||
| struct TestTypeOrder_LastIn; | ||
| struct TestTypeOrder_FirstIn; | ||
| struct TestTypeOrder_FirstIn_LastOut; | ||
| struct TestTypeOrder_LastIn_FirstOut; | ||
|
|
||
| /* | ||
| * These classes are meant to be used as singleton and check the order that they are created and destroyed. | ||
| * They fail in ctor or dtor asserting if they are not created before or after others. | ||
| */ | ||
|
|
||
| /** | ||
| * This class is supposed to be created after TestTypeOrder_FirstIn_LastOut, so check the value in ctor and modify it. | ||
| */ | ||
| struct TestTypeOrder_LastIn | ||
| { | ||
| TestTypeOrder_LastIn() | ||
| { | ||
| check_correct(); | ||
| value_to_check = 200; | ||
| } | ||
|
|
||
| void check_correct() | ||
| { | ||
| ASSERT_EQ(value_to_check, 100); | ||
| } | ||
|
|
||
| }; | ||
|
|
||
| /** | ||
| * This class is supposed to be created before TestTypeOrder_LastIn. | ||
| * It checks in construction that TestTypeOrder_LastIn still does not exist, and set value. | ||
| */ | ||
| struct TestTypeOrder_FirstIn | ||
| { | ||
| TestTypeOrder_FirstIn() | ||
| { | ||
| check_correct_ctor(); | ||
| value_to_check = 100; | ||
| } | ||
|
|
||
| void check_correct_ctor() | ||
| { | ||
| ASSERT_EQ(value_to_check, 0); | ||
| } | ||
|
|
||
| }; | ||
|
|
||
|
|
||
| /** | ||
| * This class is supposed to be created before TestTypeOrder_LastIn_FirstOut. | ||
| * It sets value when it starts and check that when leaving the value has already changed. | ||
| */ | ||
| struct TestTypeOrder_FirstIn_LastOut | ||
| { | ||
| TestTypeOrder_FirstIn_LastOut() | ||
| { | ||
| check_correct_ctor(); | ||
| value_to_check = 100; | ||
| } | ||
|
|
||
| ~TestTypeOrder_FirstIn_LastOut() | ||
| { | ||
| check_correct_dtor(); | ||
| } | ||
|
|
||
| void check_correct_ctor() | ||
| { | ||
| ASSERT_EQ(value_to_check, 0); | ||
| } | ||
|
|
||
| void check_correct_dtor() | ||
| { | ||
| ASSERT_EQ(value_to_check, 200); | ||
| } | ||
|
|
||
| }; | ||
|
|
||
| /** | ||
| * This class is supposed to be created after TestTypeOrder_LastIn or TestTypeOrder_LastIn_FirstOut. | ||
| * Checks value in creation and set it again in destruction. | ||
| */ | ||
| struct TestTypeOrder_LastIn_FirstOut | ||
| { | ||
| TestTypeOrder_LastIn_FirstOut() | ||
| : lock_reference_so_it_cannot_be_destroyed_before_this_( | ||
| Singleton<TestTypeOrder_FirstIn_LastOut>::get_shared_instance()) | ||
| { | ||
| check_correct(); | ||
| } | ||
|
|
||
| ~TestTypeOrder_LastIn_FirstOut() | ||
| { | ||
| value_to_check = 200; | ||
| } | ||
|
|
||
| void check_correct() | ||
| { | ||
| ASSERT_EQ(value_to_check, 100); | ||
| } | ||
|
|
||
| std::shared_ptr<TestTypeOrder_FirstIn_LastOut> lock_reference_so_it_cannot_be_destroyed_before_this_; | ||
| }; | ||
|
|
||
| } /* namespace test */ | ||
|
|
||
| TEST(singletonOrderTest, correct_construction_order) | ||
| { | ||
| // First call get_instance of the supposed to be created first | ||
| Singleton<test::TestTypeOrder_FirstIn>::get_instance(); | ||
|
|
||
| // call get_isntance of the one supposed to be constructed afterwards | ||
| Singleton<test::TestTypeOrder_LastIn>::get_instance(); | ||
|
|
||
| // Let singleton destroy by themselves | ||
| } | ||
|
|
||
| TEST(singletonOrderTest, correct_destruction_order) | ||
| { | ||
| // First call get_instance of the supposed to be created first | ||
| Singleton<test::TestTypeOrder_FirstIn_LastOut>::get_instance(); | ||
|
|
||
| // call get_isntance of the one supposed to be constructed afterwards | ||
| Singleton<test::TestTypeOrder_LastIn_FirstOut>::get_instance(); | ||
|
|
||
| // Let singleton destroy by themselves and fail if they should be destroyed in different order | ||
| } | ||
|
|
||
| int main( | ||
| int argc, | ||
| char** argv) | ||
| { | ||
| ::testing::InitGoogleTest(&argc, argv); | ||
| return RUN_ALL_TESTS(); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.