improve container implementation

This commit is contained in:
falsycat 2023-10-01 14:53:42 +09:00
parent ab9e7f4a3d
commit 9bae60011e
4 changed files with 59 additions and 104 deletions

View File

@ -29,22 +29,17 @@ class EnvFixture : public ::testing::Test {
protected: protected:
template <typename I> template <typename I>
void Install(const std::shared_ptr<I>& o) { void Install(SimpleEnv::ObjectOrFactory&& v) {
fmap_.emplace(typeid(I), [o](auto&) { return o; }); map_.emplace(typeid(I), std::move(v));
}
template <typename I>
void Install(std::function<std::shared_ptr<I>(Env&)>&& factory) {
fmap_.emplace(typeid(I), std::move(factory));
} }
template <typename I, typename T> template <typename I, typename T>
void Install() { void Install() {
fmap_.emplace( map_.insert(SimpleEnv::MakeItem<I, T>());
typeid(I), [](auto& env) { return std::make_shared<T>(env); });
} }
protected: protected:
void SetUp() override { void SetUp() override {
env_.emplace(std::move(fmap_)); env_.emplace(std::move(map_));
} }
void TearDown() override { void TearDown() override {
env_ = std::nullopt; env_ = std::nullopt;
@ -54,7 +49,7 @@ class EnvFixture : public ::testing::Test {
Env& env() noexcept { return *env_; } Env& env() noexcept { return *env_; }
private: private:
SimpleEnv::FactoryMap fmap_; SimpleEnv::Map map_;
std::optional<SimpleEnv> env_; std::optional<SimpleEnv> env_;
}; };

View File

@ -6,18 +6,16 @@
#include "iface/env.hh" #include "iface/env.hh"
static inline std::shared_ptr<nf7::Env> MakeEmptyEnv() { static inline bool MatchPair(
return std::make_shared<nf7::SimpleEnv>(nf7::SimpleEnv::FactoryMap {}); const std::optional<nf7::subsys::MetaEnv::Pair>& a,
} const nf7::subsys::MetaEnv::Pair& b) {
static inline bool MatchPair(const std::optional<nf7::subsys::MetaEnv::Pair>& a,
const nf7::subsys::MetaEnv::Pair& b) {
return a && a->first == b.first && &a->second == &b.second; return a && a->first == b.first && &a->second == &b.second;
} }
TEST(MetaEnv, FindOrByName) { TEST(MetaEnv, FindOrByName) {
const auto a = MakeEmptyEnv(); const auto a = std::make_shared<nf7::SimpleEnv>();
const auto b = MakeEmptyEnv(); const auto b = std::make_shared<nf7::SimpleEnv>();
const auto c = MakeEmptyEnv(); const auto c = std::make_shared<nf7::SimpleEnv>();
nf7::core::MetaEnv sut { nf7::core::MetaEnv sut {
{ {
@ -35,9 +33,9 @@ TEST(MetaEnv, FindOrByName) {
} }
TEST(MetaEnv, FindOrByIndex) { TEST(MetaEnv, FindOrByIndex) {
const auto a = MakeEmptyEnv(); const auto a = std::make_shared<nf7::SimpleEnv>();
const auto b = MakeEmptyEnv(); const auto b = std::make_shared<nf7::SimpleEnv>();
const auto c = MakeEmptyEnv(); const auto c = std::make_shared<nf7::SimpleEnv>();
nf7::core::MetaEnv sut { nf7::core::MetaEnv sut {
{ {
@ -54,9 +52,9 @@ TEST(MetaEnv, FindOrByIndex) {
} }
TEST(MetaEnv, FetchAll) { TEST(MetaEnv, FetchAll) {
const auto a = MakeEmptyEnv(); const auto a = std::make_shared<nf7::SimpleEnv>();
const auto b = MakeEmptyEnv(); const auto b = std::make_shared<nf7::SimpleEnv>();
const auto c = MakeEmptyEnv(); const auto c = std::make_shared<nf7::SimpleEnv>();
nf7::core::MetaEnv sut { nf7::core::MetaEnv sut {
{ {

View File

@ -8,6 +8,7 @@
#include <typeindex> #include <typeindex>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include <variant>
#include <vector> #include <vector>
#include "iface/common/exception.hh" #include "iface/common/exception.hh"
@ -27,7 +28,6 @@ class Container {
Container& operator=(Container&&) = delete; Container& operator=(Container&&) = delete;
virtual std::shared_ptr<I> Get(std::type_index) = 0; virtual std::shared_ptr<I> Get(std::type_index) = 0;
virtual bool installed(std::type_index) const noexcept = 0;
template <typename I2> template <typename I2>
void Get(std::shared_ptr<I2>& out) { void Get(std::shared_ptr<I2>& out) {
@ -57,42 +57,34 @@ class Container {
const auto& ret = Get(idx); const auto& ret = Get(idx);
return nullptr != ret? ret: def; return nullptr != ret? ret: def;
} }
template <typename T>
bool installed() const noexcept {
return installed(typeid(T));
}
}; };
template <typename I> template <typename I>
class NullContainer : public Container<I> { class NullContainer : public Container<I> {
public: public:
static std::shared_ptr<NullContainer> instance() static inline const auto kInstance = std::make_shared<NullContainer>();
try {
static const auto kInstance = std::make_shared<NullContainer>();
return kInstance;
} catch (const std::bad_alloc&) {
throw MemoryException {};
}
public: public:
NullContainer() = default; NullContainer() = default;
public: public:
std::shared_ptr<I> Get(std::type_index) override { return nullptr; } std::shared_ptr<I> Get(std::type_index) override { return nullptr; }
bool installed(std::type_index) const noexcept override { return false; }
}; };
template <typename I> template <typename I>
class SimpleContainer : public Container<I> { class SimpleContainer : public Container<I> {
public: public:
using Factory = std::function<std::shared_ptr<I>(Container<I>&)>; using Object = std::shared_ptr<I>;
using FactoryPair = std::pair<std::type_index, Factory>; using Factory = std::function<Object(Container<I>&)>;
using FactoryMap = std::unordered_map<std::type_index, Factory>;
using ObjectMap = std::unordered_map<std::type_index, std::shared_ptr<I>>;
using ObjectOrFactory = std::variant<Object, Factory>;
using MapItem = std::pair<std::type_index, ObjectOrFactory>;
using Map = std::unordered_map<std::type_index, ObjectOrFactory>;
public:
template <typename I2, typename T> template <typename I2, typename T>
static FactoryPair MakePair() noexcept { static MapItem MakeItem() noexcept {
static_assert(std::is_base_of_v<I, I2>, static_assert(std::is_base_of_v<I, I2>,
"registerable interface must be based on " "registerable interface must be based on "
"container common interface"); "container common interface");
@ -102,62 +94,47 @@ class SimpleContainer : public Container<I> {
static_assert(std::is_constructible_v<T, Container<I>&>, static_assert(std::is_constructible_v<T, Container<I>&>,
"registerable concrete type must be " "registerable concrete type must be "
"constructible with container"); "constructible with container");
return FactoryPair { return MapItem {
typeid(I2), [](auto& x) { return std::make_shared<T>(x); }}; typeid(I2),
} [](auto& x) { return std::make_shared<T>(x); },
static std::shared_ptr<Container<I>> Make(FactoryMap&& factories) { };
try {
return std::make_shared<Container<I>>(std::move(factories));
} catch (const std::bad_alloc&) {
throw MemoryException {};
}
} }
SimpleContainer() = delete; public:
SimpleContainer(FactoryMap&& factories, SimpleContainer(Map&& m = {},
Container<I>& fb = *NullContainer<I>::instance()) noexcept Container<I>& fb = *NullContainer<I>::kInstance) noexcept
: fallback_(fb), factories_(std::move(factories)) { } : fallback_(fb), map_(std::move(m)) { }
std::shared_ptr<I> Get(std::type_index idx) override { public:
const auto obj_itr = objs_.find(idx); Object Get(std::type_index idx) override {
if (objs_.end() != obj_itr) { assert(nest_ < 1000 && "circular dependency detected");
return obj_itr->second;
auto itr = map_.find(idx);
if (map_.end() == itr) {
return fallback_.Get(idx);
} }
const auto factory_itr = factories_.find(idx); auto& v = itr->second;
if (factories_.end() != factory_itr) { auto ret = Object {nullptr};
assert(nest_ < 1000 && if (std::holds_alternative<Object>(v)) {
"circular dependency detected in container factory"); ret = std::get<Object>(v);
} else {
++nest_; ++nest_;
auto obj = factory_itr->second(*this); ret = std::get<Factory>(v)(*this);
--nest_; --nest_;
v = ret;
try {
const auto [itr, added] = objs_.insert({idx, std::move(obj)});
(void) itr;
(void) added;
assert(added);
return itr->second;
} catch (...) {
throw MemoryException {};
}
} }
return fallback_.Get(idx); return nullptr != ret? ret:
} throw Exception {"the specified interface is hidden"};
bool installed(std::type_index idx) const noexcept override {
return factories_.contains(idx) || fallback_.installed(idx);
} }
using Container<I>::Get; using Container<I>::Get;
using Container<I>::GetOr; using Container<I>::GetOr;
using Container<I>::installed;
private: private:
Container<I>& fallback_; Container<I>& fallback_;
FactoryMap factories_; Map map_;
ObjectMap objs_;
uint32_t nest_ = 0; uint32_t nest_ = 0;
}; };

View File

@ -35,15 +35,15 @@ class BRecursive : public IB {
TEST(SimpleContainer, FetchIsolated) { TEST(SimpleContainer, FetchIsolated) {
SUT sut {{ SUT sut {{
SUT::MakePair<IA, A>(), SUT::MakeItem<IA, A>(),
}}; }};
auto ptr = sut.Get<IA>(); auto ptr = sut.Get<IA>();
EXPECT_TRUE(std::dynamic_pointer_cast<A>(ptr)); EXPECT_TRUE(std::dynamic_pointer_cast<A>(ptr));
} }
TEST(SimpleContainer, FetchDepending) { TEST(SimpleContainer, FetchDepending) {
SUT sut {{ SUT sut {{
SUT::MakePair<IA, A>(), SUT::MakeItem<IA, A>(),
SUT::MakePair<IB, B>(), SUT::MakeItem<IB, B>(),
}}; }};
auto ptr = sut.Get<IB>(); auto ptr = sut.Get<IB>();
EXPECT_TRUE(std::dynamic_pointer_cast<B>(ptr)); EXPECT_TRUE(std::dynamic_pointer_cast<B>(ptr));
@ -54,21 +54,14 @@ TEST(SimpleContainer, FetchUnknown) {
} }
TEST(SimpleContainer, FetchUnknownDepending) { TEST(SimpleContainer, FetchUnknownDepending) {
SUT sut {{ SUT sut {{
SUT::MakePair<IB, B>(), SUT::MakeItem<IB, B>(),
}}; }};
EXPECT_THROW(sut.Get<IB>(), nf7::Exception); EXPECT_THROW(sut.Get<IB>(), nf7::Exception);
} }
TEST(SimpleContainer, CheckInstalled) {
SUT sut {{
SUT::MakePair<IA, A>(),
}};
EXPECT_TRUE(sut.installed<IA>());
EXPECT_FALSE(sut.installed<IB>());
}
TEST(SimpleContainer, FetchWithFallback) { TEST(SimpleContainer, FetchWithFallback) {
SUT fb {{ SUT fb {{
SUT::MakePair<IA, A>(), SUT::MakeItem<IA, A>(),
}}; }};
SUT sut {{}, fb}; SUT sut {{}, fb};
auto ptr = sut.Get<IA>(); auto ptr = sut.Get<IA>();
@ -79,19 +72,11 @@ TEST(SimpleContainer, FetchUnknownWithFallback) {
SUT sut {{}, fb}; SUT sut {{}, fb};
EXPECT_THROW(sut.Get<IA>(), nf7::Exception); EXPECT_THROW(sut.Get<IA>(), nf7::Exception);
} }
TEST(SimpleContainer, CheckInstalledWithFallback) {
SUT fb {{
SUT::MakePair<IA, A>(),
}};
SUT sut {{}, fb};
EXPECT_TRUE(sut.installed<IA>());
EXPECT_FALSE(sut.installed<IB>());
}
#if !defined(NDEBUG) #if !defined(NDEBUG)
TEST(SimpleContainer, DeathByFetchRecursive) { TEST(SimpleContainer, DeathByFetchRecursive) {
SUT sut {{ SUT sut {{
SUT::MakePair<IB, BRecursive>(), SUT::MakeItem<IB, BRecursive>(),
}}; }};
ASSERT_DEATH_IF_SUPPORTED(sut.Get<IB>(), ""); ASSERT_DEATH_IF_SUPPORTED(sut.Get<IB>(), "");
} }