Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions cpp_utils/include/cpp_utils/types/Singleton.hpp
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;
Comment thread
juanlofer-eprosima marked this conversation as resolved.
};

} /* namespace utils */
} /* namespace eprosima */

// Include implementation template file
#include <cpp_utils/types/impl/Singleton.ipp>
39 changes: 39 additions & 0 deletions cpp_utils/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());
Comment thread
juanlofer-eprosima marked this conversation as resolved.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

@jparisu jparisu Nov 28, 2022

Choose a reason for hiding this comment

The 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.
However, I still think that auto x = shared_ptr<int>(3); delete x.get(); is a possible code, and shared_ptr does not care. We might give the user some credit and trust its intelect 😋 .

return instance_;
}

} /* namespace utils */
} /* namespace eprosima */
77 changes: 77 additions & 0 deletions cpp_utils/test/unittest/types/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.

############################
# FUZZY TEST
############################

set(TEST_NAME fuzzyTest)

set(TEST_SOURCES
Expand Down Expand Up @@ -39,3 +43,76 @@ add_unittest_executable(
"${TEST_EXTRA_LIBRARIES}"
"${TEST_NEEDED_SOURCES}"
)

############################
# SINGLETON TEST
############################

set(TEST_NAME singletonTest)

set(TEST_SOURCES
singletonTest.cpp
${PROJECT_SOURCE_DIR}/src/cpp/wait/BooleanWaitHandler.cpp
${PROJECT_SOURCE_DIR}/src/cpp/utils.cpp
${PROJECT_SOURCE_DIR}/src/cpp/time/time_utils.cpp
${PROJECT_SOURCE_DIR}/src/cpp/Formatter.cpp
)

set(TEST_LIST
trivial_get_instance
different_index_class
)

set(TEST_EXTRA_LIBRARIES
fastcdr
fastrtps
$<$<BOOL:${WIN32}>:iphlpapi$<SEMICOLON>Shlwapi>
)

set(TEST_NEEDED_SOURCES
)

add_unittest_executable(
"${TEST_NAME}"
"${TEST_SOURCES}"
"${TEST_LIST}"
"${TEST_EXTRA_LIBRARIES}"
"${TEST_NEEDED_SOURCES}"
)


############################
# SINGLETON ORDER TEST
############################

set(TEST_NAME singletonOrderTest)

set(TEST_SOURCES
singletonOrderTest.cpp
${PROJECT_SOURCE_DIR}/src/cpp/wait/BooleanWaitHandler.cpp
${PROJECT_SOURCE_DIR}/src/cpp/utils.cpp
${PROJECT_SOURCE_DIR}/src/cpp/time/time_utils.cpp
${PROJECT_SOURCE_DIR}/src/cpp/Formatter.cpp
)

set(TEST_LIST
correct_construction_order
correct_destruction_order
)

set(TEST_EXTRA_LIBRARIES
fastcdr
fastrtps
$<$<BOOL:${WIN32}>:iphlpapi$<SEMICOLON>Shlwapi>
)

set(TEST_NEEDED_SOURCES
)

add_unittest_executable(
"${TEST_NAME}"
"${TEST_SOURCES}"
"${TEST_LIST}"
"${TEST_EXTRA_LIBRARIES}"
"${TEST_NEEDED_SOURCES}"
)
169 changes: 169 additions & 0 deletions cpp_utils/test/unittest/types/singletonOrderTest.cpp
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();
}
Loading