I am trying to build a generic push component.
I have a class store<T>
which
void push(const T & t)
method called by data providers.When push is called by a provider, I want a value computed by a std::function< T2(const T&)>
and all clients (store<T2>
) are notified
with that result value of type T2. A store client has first to subscribe to store via a linker<T, T2>
object.
template <class data> class store
{
data data_;
std::list< action<data > > m_links;
public:
void push(const data & e)
{
data_ = e;
for(action<data> a : m_links)
a(data_);
}
void subscribe(action<data> d)
{
m_links.push_back(d);
}
};
Linker object :
template < class data1, class data2 > class linker
{
// source where come the push calls
store<data1> & m_source;
// targets to be notified after computation
std::list<store<data2> * > m_targets;
// computation function
std::function< data2(const data1 &)> m_func;
public:
linker(store<data1> & source, std::function< data2(const data1 &)> func)
: m_source(source), m_func(func)
{
m_source.subscribe([this](const data1 & d){this->push(d);});
}
// add a client
void add_target(store<data2> & target)
{
m_targets.push_back(&target);
}
void push(const data1 & e)
{
//compute result
data2 d2 = m_func(e);
// notify all
for(store<data2> * s2 : m_targets)
{
s2->push(d2);
}
}
};
Use case :
int main()
{
// function : just increment an int, return as double
std::function<double(const int &) > f_inc = [](const int& i){ return i+1;};
// function : display the data
std::function<int(const double&) > f_display = [](const double& d){ std::cout << "value=" << d << std::endl ; return 0;};
store<int> source;
store<double> target, target2;
linker<int, double> l(source, f_inc);
l.add_target(target);
l.add_target(target2);
linker<double, int> display(target, f_display);
source.push(1);
return 0;
}
I want to suppress the explicit-ness of the 'linker' object. I did not succeed because I dont know how to handle the fact that when a store client subscribes to a store object, the object can not store a pointeur to store since it does not know the type T2 !
I would like to write something like that:
std::function<double(const int &) > f_inc = [](const int& i){ return i+1;};
store<int> source;
store<double> target;
source.link_to(target, f_inc);
and be able to unsubscribe :
source.unlink(target, f_inc);
or with ids:
id i = source.link_to(target, f_inc);
source.unsubscribe(i);
I am using codeblocks + mingw 4.8.1 under windows xp. I guess a design pattern exists for this use case ...
ps: I cant use boost.
It seems to me that by explicitness you mean the fact that Linker
has template parameters.
I would envision something like:
class Broker {
public:
Broker(): _lastId(0) {}
//
// Notification
//
template <typename T>
void notify(store<T> const& source, T const& event) {
auto const it = _sources.find(&source);
if (it == _sources.end()) { return; }
for (size_t id: it->second) { _targets.find(id)->second->invoke(&event); }
} // notify
//
// Subscription
//
template <typename T, typename U>
size_t subscribe(Store<T> const& source, U&& callback) {
_targets[++_lastId] = std::unique_ptr<Action>(new ActionT<T>(callback));
_sources[&source].insert(_lastId);
return _lastId;
} // subscribe
template <typename T, typename U>
size_t subscribe(Store<T> const& source, U const& callback) {
return this->subscribe(source, U{callback});
} // subscribe
void unsubscribe(size_t id) {
auto const it = _targets.find(id);
if (it == _targets.end()) { return; }
void const* source = it->second->_source;
auto const it2 = _sources.find(source);
assert(it != _sources.end());
it2->second.erase(id);
if (it2->second.empty()) { _sources.erase(it2); }
_targets.erase(it);
} // unsubscribe
template <typename T>
void unsubscribe(store<T> const& source) {
auto const it = _sources.find(&source);
if (it == _sources.end()) { return; }
for (size_t id: it->second) { _targets.erase(id); }
_sources.erase(it);
} // unsubscribe
private:
//
// Action/ActionT<T> perform Type Erasure (here, we erase T)
//
struct Action {
Action(void const* source): _source(source) {}
virtual void invoke(void const*) = 0;
void const* _source;
}; // struct Action
template <typename T>
class ActionT: Action {
public:
ActionT(store<T> const& source, std::function<void(T)> f):
Action(&source),
_callback(std::move(f))
{}
virtual void invoke(void const* event) {
_callback(T(*static_cast<T const*>(event));
}
private:
std::function<void(T)> _callback;
}; // class ActionT
using Targets = std::map<size_t, std::unique_ptr<Action>>;
using Sources = std::map<void const*, std::set<size_t>>;
size_t _lastId;
Targets _targets;
Sources _sources;
}; // class Broker
As you may see, fairly complicated... and the worst of it ? It is still unsafe. Specifically, there are lifetime issues:
There are some ways to work around it, you might want to look into signal/slots which help implementing this logic without tying it to a specific object.