Skip to content
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

Add NeverDestroyed utility class #31

Merged
merged 7 commits into from
Dec 9, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
68 changes: 68 additions & 0 deletions include/ignition/utils/NeverDestroyed.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#ifndef IGNITION_UTILS_NEVERDESTROYED_HH_
#define IGNITION_UTILS_NEVERDESTROYED_HH_

#include <new>
#include <type_traits>
#include <utility>

namespace ignition
{
namespace utils
{

/// Originally copied from https://github.com/RobotLocomotion/drake/blob/v0.36.0/common/never_destroyed.h
/// Originally licensed BSD 3-Clause (https://github.com/RobotLocomotion/drake/blob/v0.36.0/LICENSE.TXT)

/// Wraps an underlying type T such that its storage is a direct member field
/// of this object (i.e., without any indirection into the heap), but *unlike*
/// most member fields T's destructor is never invoked.
///
/// This is especially useful for function-local static variables that are not
/// trivially destructable. We shouldn't call their destructor at program exit
/// because of the "indeterminate order of ... destruction" as mentioned in
/// cppguide's
/// <a href="https://drake.mit.edu/styleguide/cppguide.html#Static_and_Global_Variables">Static
mjcarroll marked this conversation as resolved.
Show resolved Hide resolved
/// and Global Variables</a> section, but other solutions to this problem place
/// the objects on the heap through an indirection.
///
/// Compared with other approaches, this mechanism more clearly describes the
/// intent to readers, avoids "possible leak" warnings from memory-checking
/// tools, and is probably slightly faster.
template <typename T>
class NeverDestroyed {
public:
/// Passes the constructor arguments along to T using perfect forwarding.
template <typename... Args>
explicit NeverDestroyed(Args&&... args) {
// Uses "placement new" to construct a `T` in `storage_`.
new (&storage_) T(std::forward<Args>(args)...);
}

/// Does nothing. Guaranteed!
~NeverDestroyed() = default;

/// \brief Deleted copy constructor
NeverDestroyed(const NeverDestroyed&) = delete;

/// \brief Deleted move constructor
NeverDestroyed(NeverDestroyed&&) = delete;

/// \brief Deleted copy assignment constructor
NeverDestroyed& operator=(const NeverDestroyed&) = delete;

/// \brief Deleted move assignment constructor
NeverDestroyed& operator=(NeverDestroyed&&) noexcept = delete;

/// Returns the underlying T reference.
T& access() { return *reinterpret_cast<T*>(&storage_); }
mjcarroll marked this conversation as resolved.
Show resolved Hide resolved
const T& access() const { return *reinterpret_cast<const T*>(&storage_); }

private:
typename std::aligned_storage<sizeof(T), alignof(T)>::type storage_;
mjcarroll marked this conversation as resolved.
Show resolved Hide resolved
};

} // namespace utils
} // namespace ignition

#endif // IGNITION_UTILS_NEVERDESTROYED_HH_

95 changes: 95 additions & 0 deletions src/NeverDestroyed_TEST.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include <gtest/gtest.h>

#include <random>
#include <unordered_map>

#include "ignition/utils/NeverDestroyed.hh"

class Boom : public std::exception { };
struct DtorGoesBoom {
~DtorGoesBoom() noexcept(false) { throw Boom(); }
mjcarroll marked this conversation as resolved.
Show resolved Hide resolved
};

// Confirm that we see booms by default.
GTEST_TEST(NeverDestroyed, BoomTest) {
try {
{ DtorGoesBoom foo; }
GTEST_FAIL();
} catch (const Boom&) {
ASSERT_TRUE(true);
}
}

// Confirm that our wrapper stops the booms.
GTEST_TEST(NeverDestroyed, NoBoomTest) {
try {
{ ignition::utils::NeverDestroyed<DtorGoesBoom> foo; }
ASSERT_TRUE(true);
} catch (const Boom& e) {
GTEST_FAIL();
}
}

// This is an example from the class overview API docs; we repeat it here to
mjcarroll marked this conversation as resolved.
Show resolved Hide resolved
// ensure it remains valid.
class Singleton {
public:
Singleton(const Singleton&) = delete;
void operator=(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
void operator=(Singleton&&) = delete;

static Singleton& getInstance() {
static ignition::utils::NeverDestroyed<Singleton> instance;
return instance.access();
}
private:
friend ignition::utils::NeverDestroyed<Singleton>;
Singleton() = default;
};

GTEST_TEST(NeverDestroyedExample, Singleton) {
const Singleton* get1 = &Singleton::getInstance();
const Singleton* get2 = &Singleton::getInstance();
EXPECT_EQ(get1, get2);
}

// This is an example from the class overview API docs; we repeat it here to
// ensure it remains valid.
enum class Foo { kBar, kBaz };
Foo ParseFoo(const std::string& foo_string) {
using Dict = std::unordered_map<std::string, Foo>;
static const ignition::utils::NeverDestroyed<Dict> string_to_enum{
std::initializer_list<Dict::value_type>{
{"bar", Foo::kBar},
{"baz", Foo::kBaz},
}
};
return string_to_enum.access().at(foo_string);
}

GTEST_TEST(NeverDestroyedExample, ParseFoo) {
EXPECT_EQ(ParseFoo("bar"), Foo::kBar);
EXPECT_EQ(ParseFoo("baz"), Foo::kBaz);
}

// This is an example from the class overview API docs; we repeat it here to
// ensure it remains valid.
const std::vector<double>& GetConstantMagicNumbers() {
static const
ignition::utils::NeverDestroyed<std::vector<double>> result{[]() {
std::vector<double> prototype;
std::mt19937 random_generator;
for (int i = 0; i < 10; ++i) {
double new_value = random_generator();
prototype.push_back(new_value);
}
return prototype;
}()};
return result.access();
}

GTEST_TEST(NeverDestroyedExample, GetConstantMagicNumbers) {
const auto& numbers = GetConstantMagicNumbers();
EXPECT_EQ(numbers.size(), 10u);
}