Skip to content

Instantly share code, notes, and snippets.

@Jiwan
Created February 4, 2016 22:05
Show Gist options
  • Save Jiwan/31f8f837e4f4b90fed13 to your computer and use it in GitHub Desktop.
Save Jiwan/31f8f837e4f4b90fed13 to your computer and use it in GitHub Desktop.
Final C++14 example for my post - An introduction to C++'s variadic templates: a thread-safe multi-type map
#include <iostream>
#include <memory>
#include <string>
#include "repository.hpp"
// Incomplete types used as compile-time keys.
struct Key1;
struct Key2;
// Create a type for our repository.
using MyRepository = Repository
<
Slot<std::string>, // One slot for std::string.
Slot<int, Key1>, // Two slots for int.
Slot<int, Key2> // Must be differentiate using "type keys" (Key1, Key2).
>;
int main()
{
MyRepository myRepository;
myRepository.emplace<std::string>("test"); // Construct the shared_ptr within the repository.
myRepository.emplace<int, Key1>(1337);
myRepository.set<int, Key2>(std::make_shared<int>(42)); // Set the shared_ptr manually.
// Note: I use '*' as get returns a shared_ptr.
std::cout << *myRepository.get<std::string>() << std::endl; // Print "test".
std::cout << *myRepository.get<int, Key1>() << std::endl; // Print 1337.
std::cout << *myRepository.get<int, Key2>() << std::endl; // Print 42.
// std::cout << *myRepository.get<int>() << std::endl;
// ^^^ Compilation error: which int shall be selected? Key1 or Key2?
auto watcher = myRepository.getWatcher<std::string>(); // Create a watcher object to observe changes on std::string.
std::cout << watcher->hasBeenChanged() << std::endl; // 0: no changes since the watcher creation.
myRepository.emplace<std::string>("yo"); // Emplace a new value into the std::string slot.
std::cout << watcher->hasBeenChanged() << std::endl; // 1: the std::string slot has been changed.
std::cout << *watcher->get() << std::endl; // Poll the value and print "yo".
std::cout << watcher->hasBeenChanged() << std::endl; // 0: no changes since the last polling.
return EXIT_SUCCESS;
}
//
// Created by jguegant on 1/30/16.
//
#ifndef REPOSITORY_HPP
#define REPOSITORY_HPP
#include <atomic>
#include <iostream>
#include <memory>
#include <mutex>
#include <vector>
struct DefaultSlotKey;
template <class Type, class Key = DefaultSlotKey> class Slot;
template <class Type, class Key>
class Watcher
{
public:
Watcher(Slot<Type, Key>& slot):
slot_(slot),
hasBeenChanged_(false)
{
}
Watcher(const Watcher&) = delete;
Watcher & operator=(const Watcher&) = delete;
bool hasBeenChanged() const
{
return hasBeenChanged_;
}
void triggerChanges()
{
hasBeenChanged_ = true;
}
auto get() -> decltype(std::declval<Slot<Type, Key>>().doGet())
{
hasBeenChanged_ = false; // Note: even if there is an update of the value between this line and the getValue one,
// we will still have the latest version.
// Note 2: atomic_bool automatically use a barrier and the two operations can't be inversed.
return slot_.doGet();
}
private:
Slot<Type, Key>& slot_;
std::atomic_bool hasBeenChanged_;
};
template <class Type, class Key = DefaultSlotKey>
using WatcherPtr = std::unique_ptr<Watcher<Type, Key>,
std::function<void(Watcher<Type, Key>*)>>;
template <class Type, class Key>
class Slot
{
public:
using ThisType = Slot<Type, Key>;
using WatcherType = Watcher<Type, Key>;
using WatcherTypePtr = std::unique_ptr<WatcherType, std::function<void(WatcherType*)>> ;
public:
std::shared_ptr<Type> doGet() const
{
return std::atomic_load(&value_);
}
void doSet(const std::shared_ptr<Type> &value)
{
std::atomic_exchange(&value_, value);
signal();
}
WatcherTypePtr doGetWatcher()
{
WatcherTypePtr watcher(new WatcherType(*this), [this](WatcherType* toBeDelete) {
this->unregisterWatcher(toBeDelete);});
registerWatcher(watcher.get());
return watcher;
}
private:
void registerWatcher(WatcherType* newWatcher)
{
std::lock_guard<std::mutex> l(watchers_mutex_);
watchers_.push_back(newWatcher);
}
void unregisterWatcher(WatcherType *toBeDelete)
{
std::lock_guard<std::mutex> l(watchers_mutex_);
watchers_.erase(std::remove(watchers_.begin(), watchers_.end(), toBeDelete), watchers_.end());
delete toBeDelete;
}
void signal()
{
std::lock_guard<std::mutex> l(watchers_mutex_);
for (auto watcher : watchers_) {
watcher->triggerChanges();
}
}
private:
std::vector<WatcherType*> watchers_;
std::shared_ptr<Type> value_;
std::mutex watchers_mutex_;
};
template<class... Slots>
class Repository : private Slots... {
public:
template <class Type, class Key = DefaultSlotKey>
std::shared_ptr<Type> get()
{
static_assert(std::is_base_of<Slot<Type, Key>, Repository<Slots...>>::value,
"Please ensure that this type or this key exists in this repository");
return Slot<Type, Key>::doGet();
}
template <class Type, class Key = DefaultSlotKey>
void set(const std::shared_ptr<Type>& value)
{
static_assert(std::is_base_of<Slot<Type, Key>, Repository<Slots...>>::value,
"Please ensure that this type or this key exists in this repository");
Slot<Type, Key>::doSet(value);
}
template <class Type, class Key = DefaultSlotKey, class ...Args>
void emplace(Args&&... args)
{
static_assert(std::is_base_of<Slot<Type, Key>, Repository<Slots...>>::value,
"Please ensure that this type or this key exists in this repository");
Slot<Type, Key>::doSet(std::make_shared<Type>(std::forward<Args>(args)...));
}
template <class Type, class Key = DefaultSlotKey>
auto getWatcher()
{
static_assert(std::is_base_of<Slot<Type, Key>, Repository<Slots...>>::value,
"Please ensure that this type or this key exists in this repository");
return Slot<Type, Key>::doGetWatcher();
}
};
#endif // REPOSITORY_HPP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment