Skip to content

Instantly share code, notes, and snippets.

@kallsyms
Last active July 5, 2023 15:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kallsyms/ea9cc1fe88a26f91711d2335feafaf31 to your computer and use it in GitHub Desktop.
Save kallsyms/ea9cc1fe88a26f91711d2335feafaf31 to your computer and use it in GitHub Desktop.
Simple C++ flyweight implementation
// Simple flyweight pattern implementation.
#include <memory>
#include <mutex>
#include <unordered_set>
template <typename T>
class FlyweightObj
{
// FlyweightObj takes two forms:
// 1) A raw pointer to the object, which is used for cheap lookups.
// 2) A weak_ptr to the object, which is used to keep an actual reference to the object for later "strengthening" into a shared_ptr.
// Only instances of the 2nd form can be inserted into the pool, while the 1st form is used for lookups.
// Why? The weak_ptr isn't valid in the shared_ptr deleter, so we have to separately keep the raw pointer and use it as the comparator to find the object to be deleted.
public:
explicit FlyweightObj(T *p) : raw_(p) {}
explicit FlyweightObj(std::shared_ptr<T> p) : raw_(p.get()), weak_(p) {}
bool operator==(const FlyweightObj<T> &other) const
{
// Raw pointers will be equal in the shared_ptr deleter, so the deref won't occur.
// In all other cases, the raw pointer is guaranteed to be safe either 1) by construction or 2) since we have not encountered the shared_ptr deleter yet.
return raw_ == other.raw_ || *raw_ == *other.raw_;
}
std::shared_ptr<T> get() const
{
return weak_.lock();
}
// Use the raw pointer to hash to support "type 1" FlyweightObjs.
// N.B. This is not guaranteed safe on a "type 2" FlyweightObj, since the raw pointer is not guaranteed to be valid (again, in the shared_ptr deleter).
// However we don't need to hash the item already in the set at deletion time, just the "type 1" FlyweightObj we're using to find it.
std::size_t hash() const
{
return std::hash<typename std::remove_cv<T>::type>{}(*raw_);
}
private:
T *raw_;
std::weak_ptr<T> weak_;
};
template <typename T>
struct std::hash<FlyweightObj<T>>
{
std::size_t operator()(const FlyweightObj<T> &o) const
{
return o.hash();
}
};
template <typename T>
class FlyweightCore
{
public:
// Returned shared_ptr is const so that the shared value cannot be unintentionally modified.
using ConstT = typename std::add_const<T>::type;
static std::shared_ptr<ConstT> insert(T &&value)
{
std::lock_guard<std::mutex> guard(mtx_);
auto it = pool_.find(FlyweightObj<ConstT>(&value));
if (it != pool_.end())
{
return it->get();
}
else
{
std::shared_ptr<ConstT> ptr(new ConstT{std::move(value)}, [](ConstT *v)
{ std::lock_guard<std::mutex> guard(mtx_); pool_.erase(FlyweightObj(v)); });
pool_.insert(FlyweightObj<ConstT>(ptr));
return ptr;
}
}
// Returns the number of unique values in the pool.
// Only for testing purposes.
static size_t size()
{
return pool_.size();
}
private:
// See https://stackoverflow.com/a/76439305
// This can also be done with a using/typedef in this class, later initialized as
// typename FlyweightCore<T>::PoolType FlyweightCore<T>::pool_ = {};
static inline std::unordered_set<FlyweightObj<ConstT>> pool_ = {};
static inline std::mutex mtx_ = {};
};
// Wrapping class which holds a shared_ptr returned from FlyweightCore.
template <typename T>
class Flyweight
{
using ConstT = typename FlyweightCore<T>::ConstT;
public:
Flyweight(T &&value) : value_(FlyweightCore<T>::insert(std::move(value))) {}
ConstT &operator*() const { return *value_; }
private:
std::shared_ptr<ConstT> value_;
};
/*
* ----------------------------------------
* Example
* ----------------------------------------
*/
#include <iostream>
#include <string>
class Foo
{
public:
Foo(std::string &&name) : name_(std::move(name)) {}
Flyweight<std::string> name_;
};
struct cred
{
int uid;
int gid;
std::string username;
std::string groupname;
bool operator==(const cred &other) const
{
return std::tie(uid, gid, username, groupname) == std::tie(other.uid, other.gid, other.username, other.groupname);
}
};
template <>
struct std::hash<cred>
{
std::size_t operator()(const cred &c) const
{
return std::hash<int>()(c.uid) ^ std::hash<int>()(c.gid);
}
};
class Foo2
{
public:
Foo2(cred &&cred) : cred_(std::move(cred)) {}
Flyweight<cred> cred_;
};
int main()
{
{
std::cerr << "sizeof(FlyweightObj<std::string>) = " << sizeof(FlyweightObj<std::string>) << std::endl;
Foo foo1("foo");
Foo foo2("foo");
Foo foo3("bar");
std::cout << &(*foo1.name_) << " " << &(*foo2.name_) << " " << &(*foo3.name_) << std::endl;
std::cout << FlyweightCore<std::string>::size() << std::endl;
}
std::cout << FlyweightCore<std::string>::size() << std::endl;
{
std::cerr << "sizeof(cred) = " << sizeof(cred) << std::endl;
std::cerr << "sizeof(FlyweightObj<cred>) = " << sizeof(FlyweightObj<cred>) << std::endl;
std::cerr << "sizeof(Foo2) = " << sizeof(Foo2) << std::endl;
Foo2 foo1({0, 0, "root", "root"});
Foo2 foo2({0, 0, "root", "root"});
Foo2 foo3({1000, 1000, "user", "wheel"});
std::cout << &(*foo1.cred_) << " " << &(*foo2.cred_) << " " << &(*foo3.cred_) << std::endl;
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment