Our Python codebase has metrics-related code that looks like this:
class Timer:
def __enter__(self, name):
self.name = name
self.start = time.time()
def __exit__(self):
elapsed = time.time() - self.start
log.info('%s took %f seconds' % (self.name, elapsed))
...
with Timer('foo'):
do some work
with Timer('bar') as named_timer:
do some work
named_timer.some_mutative_method()
do some more work
In Python's terminology, the timer is a contextmanager.
Now we want to implement the same thing in C++, with an equally nice syntax. Unfortunately, C++ doesn't have with. So the "obvious" idiom would be (classical RAII)
class Timer {
Timer(std::string name) : name_(std::move(name)) {}
~Timer() { /* ... */ }
};
if (true) {
Timer t("foo");
do some work
}
if (true) {
Timer named_timer("bar");
do some work
named_timer.some_mutative_method();
do some more work
}
But this is extremely ugly syntactic salt: it's many lines longer than it needs to be, we had to introduce a name t for our "unnamed" timer (and the code breaks silently if we forget that name)... it's just ugly.
What are some syntactic idioms that people have used to deal with "contextmanagers" in C++?
I've thought of this abusive idea, which reduces the line-count but doesn't get rid of the name t:
// give Timer an implicit always-true conversion to bool
if (auto t = Timer("foo")) {
do some work
}
Or this architectural monstrosity, which I don't even trust myself to use correctly:
Timer("foo", [&](auto&) {
do some work
});
Timer("bar", [&](auto& named_timer) {
do some work
named_timer.some_mutative_method();
do some more work
});
where the constructor of Timer actually invokes the given lambda (with argument *this) and does the logging all in one go.
Neither of those ideas seems like a "best practice", though. Help me out here!
Another way to phrase the question might be: If you were designing std::lock_guard from scratch, how would you do it so as to eliminate as much boilerplate as possible? lock_guard is a perfect example of a contextmanager: it's a utility, it's intrinsically RAII, and you hardly ever want to bother naming it.
It's possible to mimic the Python syntax and semantics quite closely. The following test case compiles and has largely similar semantics to what you'd have in Python:
// https://github.com/KubaO/stackoverflown/tree/master/questions/pythonic-with-33088614
#include <cassert>
#include <cstdio>
#include <exception>
#include <iostream>
#include <optional>
#include <string>
#include <type_traits>
[...]
int main() {
// with Resource("foo"):
// print("* Doing work!\n")
with<Resource>("foo") >= [&] {
std::cout << "1. Doing work\n";
};
// with Resource("foo", True) as r:
// r.say("* Doing work too")
with<Resource>("bar", true) >= [&](auto &r) {
r.say("2. Doing work too");
};
for (bool succeed : {true, false}) {
// Shorthand for:
// try:
// with Resource("bar", succeed) as r:
// r.say("Hello")
// print("* Doing work\n")
// except:
// print("* Can't do work\n")
with<Resource>("bar", succeed) >= [&](auto &r) {
r.say("Hello");
std::cout << "3. Doing work\n";
} >= else_ >= [&] {
std::cout << "4. Can't do work\n";
};
}
}
That's given
class Resource {
const std::string str;
public:
const bool successful;
Resource(const Resource &) = delete;
Resource(Resource &&) = delete;
Resource(const std::string &str, bool succeed = true)
: str(str), successful(succeed) {}
void say(const std::string &s) {
std::cout << "Resource(" << str << ") says: " << s << "\n";
}
};
The with free function passes all the work to the with_impl class:
template <typename T, typename... Ts>
with_impl<T> with(Ts &&... args) {
return with_impl<T>(std::forward<Ts>(args)...);
}
How do we get there? First, we need a context_manager class: the traits class that implements the enter and exit methods - the equivalents of Python's __enter__ and __exit__. As soon as the is_detected type trait gets rolled into C++, this class can also easily forward to the compatible enter and exit methods of the class type T, thus mimicking Python's semantics even better. As it stands, the context manager is rather simple:
template <typename T>
class context_manager_base {
protected:
std::optional<T> context;
public:
T &get() { return context.value(); }
template <typename... Ts>
std::enable_if_t<std::is_constructible_v<T, Ts...>, bool> enter(Ts &&... args) {
context.emplace(std::forward<Ts>(args)...);
return true;
}
bool exit(std::exception_ptr) {
context.reset();
return true;
}
};
template <typename T>
class context_manager : public context_manager_base<T> {};
Let's see how this class would be specialized to wrap the Resource objects, or std::FILE *.
template <>
class context_manager<Resource> : public context_manager_base<Resource> {
public:
template <typename... Ts>
bool enter(Ts &&... args) {
context.emplace(std::forward<Ts>(args)...);
return context.value().successful;
}
};
template <>
class context_manager<std::FILE *> {
std::FILE *file;
public:
std::FILE *get() { return file; }
bool enter(const char *filename, const char *mode) {
file = std::fopen(filename, mode);
return file;
}
bool leave(std::exception_ptr) { return !file || (fclose(file) == 0); }
~context_manager() { leave({}); }
};
The implementation of the core functionality is in the with_impl type. Note how the exception handling within the suite (the first lambda) and the exit function mimic Python behavior.
static class else_t *else_;
class pass_exceptions_t {};
template <typename T>
class with_impl {
context_manager<T> mgr;
bool ok;
enum class Stage { WITH, ELSE, DONE } stage = Stage::WITH;
std::exception_ptr exception = {};
public:
with_impl(const with_impl &) = delete;
with_impl(with_impl &&) = delete;
template <typename... Ts>
explicit with_impl(Ts &&... args) {
try {
ok = mgr.enter(std::forward<Ts>(args)...);
} catch (...) {
ok = false;
}
}
template <typename... Ts>
explicit with_impl(pass_exceptions_t, Ts &&... args) {
ok = mgr.enter(std::forward<Ts>(args)...);
}
~with_impl() {
if (!mgr.exit(exception) && exception) std::rethrow_exception(exception);
}
with_impl &operator>=(else_t *) {
assert(stage == Stage::ELSE);
return *this;
}
template <typename Fn>
std::enable_if_t<std::is_invocable_r_v<void, Fn, decltype(mgr.get())>, with_impl &>
operator>=(Fn &&fn) {
assert(stage == Stage::WITH);
if (ok) try {
std::forward<Fn>(fn)(mgr.get());
} catch (...) {
exception = std::current_exception();
}
stage = Stage::ELSE;
return *this;
}
template <typename Fn>
std::enable_if_t<std::is_invocable_r_v<bool, Fn, decltype(mgr.get())>, with_impl &>
operator>=(Fn &&fn) {
assert(stage == Stage::WITH);
if (ok) try {
ok = std::forward<Fn>(fn)(mgr.get());
} catch (...) {
exception = std::current_exception();
}
stage = Stage::ELSE;
return *this;
}
template <typename Fn>
std::enable_if_t<std::is_invocable_r_v<void, Fn>, with_impl &> operator>=(Fn &&fn) {
assert(stage != Stage::DONE);
if (stage == Stage::WITH) {
if (ok) try {
std::forward<Fn>(fn)();
} catch (...) {
exception = std::current_exception();
}
stage = Stage::ELSE;
} else {
assert(stage == Stage::ELSE);
if (!ok) std::forward<Fn>(fn)();
if (!mgr.exit(exception) && exception) std::rethrow_exception(exception);
stage = Stage::DONE;
}
return *this;
}
template <typename Fn>
std::enable_if_t<std::is_invocable_r_v<bool, Fn>, with_impl &> operator>=(Fn &&fn) {
assert(stage != Stage::DONE);
if (stage == Stage::WITH) {
if (ok) try {
ok = std::forward<Fn>(fn)();
} catch (...) {
exception = std::current_exception();
}
stage = Stage::ELSE;
} else {
assert(stage == Stage::ELSE);
if (!ok) std::forward<Fn>(fn)();
if (!mgr.exit(exception) && exception) std::rethrow_exception(exception);
stage = Stage::DONE;
}
return *this;
}
};
Edit: After reading Dai's comment more carefully, and thinking a bit more, I realized this is a poor choice for C++ RAII. Why? Because you are logging in the destructor, this means you are doing io, and io can throw. C++ destructors should not emit exceptions. With python, writing a throwing __exit__ isn't necessarily awesome either, it can cause you to drop your first exception on the floor. But in python, you definitively know whether the code in the context manager has caused an exception or not. If it caused an exception, you can just omit your logging in __exit__ and pass through the exception. I leave my original answer below in case you have a context manager which doesn't risk throwing on exit.
The C++ version is 2 lines longer than the python version, one for each curly brace. If C++ is only two lines longer than python, that's doing well. Context managers are designed for this specific thing, RAII is more general and provides a strict superset of the functionality. If you want to know best practice, you already found it: have an anonymous scope and create the object at the beginning. This is idiomatic. You may find it ugly coming from python, but in the C++ world it's just fine. The same way someone from C++ will find context managers ugly in certain situations. FWIW I use both languages professionally and this doesn't bother me at all.
That said, I'll provide a cleaner approach for anonymous context managers. Your approach with constructing Timer with a lambda and immediately letting it destruct is pretty weird, so you're right to be suspicious. A better approach:
template <class F>
void with_timer(const std::string & name, F && f) {
Timer timer(name);
f();
}
Usage:
with_timer("hello", [&] {
do something;
});
This is equivalent to the anonymous context manager, in the sense that none of Timer's methods can be called other than construction and destruction. Also, it uses the "normal" class, so you can use the class when you need a named context manager, and this function otherwise. You could obviously write with_lock_guard in a very similar way. There it's even better as lock_guard doesn't have any member functions you're missing out on.
All that said, would I use with_lock_guard, or approve code written by a teammate that added in such a utility? No. One or two extra lines of code just doesn't matter; this function doesn't add enough utility to justify it's own existence. YMMV.
You don't need if( true ), C++ has "anonymous scopes" which can be used to restrict a scope's lifetime in much the same was as Python's with or C#s using (well, C# also has anonymous scopes too).
Like so:
doSomething();
{
Time timer("foo");
doSomethingElse();
}
doMoreStuff();
Just use bare curly-brackets.
However, I disagree with your idea of using RAII-semantics to instrument code like this as the timer destructor is non-trivial and has side-effects by-design. It might be ugly and repetitive, but I feel explicitly calling named startTimer, stopTimer and printTimer methods make the program more "correct" and self-documenting. Side-effects are bad, m'key?
I have recently started a C++ project to mimic Python's context manager as I migrate a python code base over to C++, found at https://github.com/batconjurer/contextual.
For this flow, you need to define a resource manager derived from an interface named IResource. Below, this is called Timer. It is in this class that the enter and exit functions are implemented. The context is just the code block that requires the resources, so it is passed via anonymous function.
The resource manager expects you to implement the IData struct which is uses to store the acquired resources. It actually only keeps a pointer to an IData instance.
For your use case, the following is an example implementation that compiles with C++17.
#include "include/contextual.h"
#include <ctime>
#include <chrono>
#include <thread>
using namespace Contextual;
namespace Contextual {
struct IData {
std::string name;
std::time_t start_time = std::time(NULL);
void reset_time() {
std::cout << "Time before restart: " << start_time << "\n";
std::time(&start_time);
std::cout << "Time after restart: " << start_time << "\n";
};
};
class Timer : public IResource<IData> {
private:
IData _data;
void enter() override {
std::time(&resources->start_time);
}
void exit(std::optional<std::exception> e) override {
double elapsed_time = std::time(NULL) - resources->start_time;
std::cout << resources->name << " took " << elapsed_time << " seconds.\n";
if (e) {
throw e.value();
}
}
public:
Timer(std::string &&name) : IResource<IData>(_data), _data(IData{name}){};
};
};
int main(){
With {
Timer(std::string("Foo")) + Context{
[&](IData* time_data) {
std::chrono::milliseconds sleeptime(5000);
std::this_thread::sleep_for(sleeptime); // In place of "Do some work"
time_data->reset_time(); // In place of "some_mutative_function()"
std::this_thread::sleep_for(sleeptime); // In place of "Do some work"
}
}
};
}
There are some nuisances I'm still working on (for example the fact that the IData struct had to be stored as a instance variable of the Timer since only a pointer to it is kept by IResource). And of course C++ exceptions aren't the nicest things.
Inspired by Dai's answer, I ended up with this code:
#include <iostream>
#include <chrono>
class Timer
{
std::chrono::high_resolution_clock::time_point startTime;
public:
Timer(): startTime(std::chrono::high_resolution_clock::now()){};
void elapsed()
{
auto endTime = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> elapsedTime = endTime - startTime;
std::cout << elapsedTime.count() << std::endl;
}
};
int main()
{
{
Timer timer=Timer();
std::cout << "This is some work" << std::endl;
timer.elapsed();
}
return 0;
}
I'm much more fluent in python than in c++; I'm not sure if it's idiomatic but it works for me.
Related
I'm trying to refactor some code. Basically is a state machine based with enum.
There are a lot of switch statements and functions that got called with different names and ambiguations.
Since they force me to keep the enum, I would like to refactor it using template. Basically I would like to use template to implement polymorphism. Since the states are limited there should be a way but I cannot find the best one.
#include <iostream>
enum class AnimalType
{
Dog,
Cat
};
template<AnimalType T>
void Foo()
{
std::cout << "Unknown animal\n";
}
template<>
void Foo<AnimalType::Dog>()
{
std::cout << "I'm a dog\n";
}
template<>
void Foo<AnimalType::Cat>()
{
std::cout << "I'm a cat\n";
}
int main()
{
AnimalType CurrentAnimal = AnimalType::Dog;
// Foo<CurrentAnimal>(); Won't compile
return 0;
}
You need a compile time evaluatable constant, this will work
int main()
{
constexpr auto CurrentAnimal = AnimalType::Dog;
Foo<CurrentAnimal>();
return 0;
}
or directly use
Foo<AnimalType::Dog>();
Note : you can't use your construct to make decissions at runtime.
Templates only lead to compile time polymorphism
As mentioned by #P Kramer's answer:
Note : you can't use your construct to make decissions at runtime. Templates only lead to compile time polymorphism.
You can't do that, but you can use the Compile-Time Dispatch and runtime parameter by passing the desired value as parameter while they are separated by Function Template Specialization. For example turn your enumerations value into actual types:
struct animal_t
{
std::string const name;
explicit animal_t(std::string const& name_)
: name(name_)
{
}
auto operator()() const
{
return name;
}
};
struct dog_t final : animal_t
{
using animal_t::animal_t;
};
struct cat_t final : animal_t
{
using animal_t::animal_t;
};
They you are able to specialize the function template:
/*!
*
* Other Programmer(s) interface
*
*/
template<typename Animal>
auto function(Animal const&)
{
assert(false);
}
/*!
*
* Implementation
*
*/
template<>
auto function(cat_t const& animal)
{
return animal();
}
template<>
auto function(dog_t const& animal)
{
return animal();
}
Now user (other programmer) of your library could easily interact with it for example by a GUI library:
QObject::connect(button1, &QPushButton::clicked, &application, [] {
cat_t cat("Some Cat");
auto const message = QString::fromStdString(function(cat));
QMessageBox::information(nullptr, " ", message);
});
QObject::connect(button2, &QPushButton::clicked, &application, [] {
dog_t dog("Some Dog");
auto const message = QString::fromStdString(function(dog));
QMessageBox::information(nullptr, " ", message);
});
Result: just for copy/past: runtime_dispatch_v1
I am trying to switch between two class objects based on a global variable DOF. The idea is to change the return type using the template class. But the first line inside main() has a compile-time error template argument deduction/substitution failed. Could you please help me to understand the problem and fix it and is there a better way of doing this? Any suggestions and help are appreciated. Thanks in advance.
#include <iostream>
#include <string>
class MM
{
public:
MM(){}
std::string printName()
{
return "MM";
}
};
class MM2
{
public:
MM2(){}
std::string printName()
{
return "MM2";
}
};
using namespace std;
const unsigned short int DOF = 7;
MM* obj = nullptr;
MM2* obj2 = nullptr;
template<class T>
T getClass()
{
if(DOF==7)
{
if(obj == nullptr)
{
obj = new MM();
}
return obj;
}
else if(DOF == 6)
{
if(obj2 == nullptr)
{
obj2 = new MM2();
}
return obj2;
}
}
int main()
{
getClass()->printName();
//std::cout << "class name " << getClass()->printName() << std::endl;
return 0;
}
That is not how templates work in C++. The type of the template parameter must be known at compile time and cannot change at runtime.
The pattern you are trying to achieve in your example scan easily be done with virtual functions: make MM and MM2 have a common base class and make printName a virtual function. While we are here: don't use manual memory management, i.e. don't use explicit new/delete. Use smart pointers like unique_ptr.
Other options are std::any and std:: variant but I wouldn't recommend them unless you have a very particular use case for them.
For your simple example an option could be to return a function pointer or a std::function. That would work on your example because your classes are stateless, but I suspect your real classes have state or more methods you wish to access in which case you shouldn't try to do this.
If you can use C++17 (and if you can't then that's a shame), you can do this if you switch things round a bit.
Firstly, use a template parameter to determine what getClass does and use if constexpr instead of just plain old if:
template<int N>
auto getClass()
{
if constexpr (N == 7)
{
if(obj == nullptr)
{
obj = new MM();
}
return obj;
}
else if constexpr (N == 6)
{
if(obj2 == nullptr)
{
obj2 = new MM2();
}
return obj2;
}
}
Then invoke this template like this:
std::cout << "class name " << getClass <DOF> ()->printName() << std::endl;
Miscellaneous notes:
All paths through getClass should return a value.
You are leaking memory by calling new and not calling delete. Better options are available.
Edit: Here's a C++11 solution using SFINAE:
template<int N, typename std::enable_if<N == 7, int>::type = 0>
MM *getClass()
{
if(obj == nullptr)
{
obj = new MM();
}
return obj;
}
template<int N, typename std::enable_if<N == 6, int>::type = 0>
MM2 *getClass()
{
if(obj2 == nullptr)
{
obj2 = new MM2();
}
return obj2;
}
And then you can still do:
std::cout << "class name " << getClass <DOF> ()->printName() << std::endl;
Live demo
This is something you might try. As others have said, templates only work at compile time: if you want to dynamically change the types later during runtime, then polymorphism is the way to go. You can use a kind of 'PIMPL' design to effectively 'insert' a base class above the MM and MM2 classes. The base class includes pure virtual functions for all the common functions for MM and MM2 that you need to access (eg printName() in this example).
#include <iostream>
#include <memory>
#include <string>
class MM
{
public:
MM() {}
std::string printName()
{
return "MM";
}
};
class MM2
{
public:
MM2() {}
std::string printName()
{
return "MM2";
}
};
class MMBase
{
public:
virtual std::string printName() = 0;
virtual ~MMBase() {}
};
//Templated wrapper for each MM class type, deriving from abstract MMBase
template<class T>
class MMWrap : public MMBase
{
std::unique_ptr<T> _impl;
public:
MMWrap() : _impl(nullptr)
{
_impl = std::make_unique<T>();
}
//Pass function call to _impl pointer
std::string printName()
{
return _impl->printName();
}
};
class MMFactory
{
public:
enum MMType {TypeMM2=6,TypeMM};
static MMType _type;
static std::unique_ptr<MMBase> getMM()
{
if (_type == TypeMM) return std::unique_ptr<MMBase>(new MMWrap<MM>());
if (_type == TypeMM2) return std::unique_ptr<MMBase>(new MMWrap<MM2>());
return nullptr; //Avoids compiler warning about not all paths return value
}
};
//Initialize static member to which default MM type is required
MMFactory::MMType MMFactory::_type = MMFactory::TypeMM;
int main()
{
std::cout<< MMFactory::getMM()->printName() << std::endl;
MMFactory::_type = MMFactory::TypeMM2;
std::cout << MMFactory::getMM()->printName() << std::endl;
}
I've put in a templated wrapper class, but that may need modification depending what parameters the MM/MM2 constructors need. Also the wrapped pointers are created within the constructor (if they throw then there might be an issue): these could be moved to a lazy evaluation model, making _impl mutable. I don't know how MM/MM2 are used later: if they have functions which take references to other MM types then a bit more work may be needed.
How to appropriately cache userData that is generated from user's callbackBegin() and send it to user's callbackEnd().
Simple version (No userData - demo)
I want to create a complex database that support callback. For MCVE, let's say it is MyArray.
Here is a simple array class that supports callback but no userData.
#include <iostream>
template<class Derived>class MyArray{ //library - I design it.
public: void push_back(int s){
static_cast<Derived*>(this)->callbackBegin(s);
//do something about array
static_cast<Derived*>(this)->callbackEnd(s);
}
//other fields / functions
};
class Callback : public MyArray<Callback>{ //user's class
public: void callbackBegin(int s){
std::cout<<"callbackBegin"<<std::endl;
}
public: void callbackEnd(int s){
std::cout<<"callbackEnd"<<std::endl;
}
};
int main() {
Callback c;
c.push_back(5); //print: callbackBegin callbackEnd
return 0;
}
It works correctly.
The next step : I want to pass some userData from Callback::callbackBegin() to Callback::callbackEnd().
For example, userData is a clock time when Callback::callbackBegin() is called.
My poor solution (void*& userdata : demo)
Here is my attempt to implement it :-
#include <iostream>
#include <time.h>
template<class Derived>class MyArray{
public: void push_back(int s){
void* userData=nullptr; //#
static_cast<Derived*>(this)->callbackBegin(s,userData); //# ugly
//do something about array
static_cast<Derived*>(this)->callbackEnd(s,userData); //# ugly
}
};
class Callback : public MyArray<Callback>{
public: void callbackBegin(int s,void*& userData){ //#
userData=new clock_t(clock()); //# danger
std::cout<<"callbackBegin"<<std::endl;
}
public: void callbackEnd(int s,void*& userData){ //#
clock_t* userDataTyped=static_cast<clock_t*>(userData);
clock_t clock2=clock();
clock_t different=clock2 - (*userDataTyped);
std::cout<<"callbackEnd time(second)="
<<((float)different)/CLOCKS_PER_SEC<<std::endl;
delete userDataTyped; //# danger
}
};
int main() {
Callback c;
c.push_back(5); //print: callbackBegin callbackEnd time(second)=8.5e-05
return 0;
}
It also works correctly, but I believe it is a bad design (at various #) :-
new/delete in 2 places : potential memory leaking.
Strong pointer is preferred, but I don't know how to.
static_cast<clock_t*>(userData) is code-smell, at least for me.
(minor issue) an extra ugly parameter void*&
Question: What are design patterns / C++ magic to avoid such issues, while make MyArray concise, easy to use, maintainable (i.e. not much worse than the Simple version)?
Other notes:
In real cases, <5% of user's callback classes need userData.
Thus, I feel very reluctant to add void&* as an extra parameter.
Clarify: (edited) The minority cases usually need different types of userData e.g. Callback1 need clock_t, Callback2 need std::string, etc.
Proposed solution should restrain from using std::function<> or virtual function, because the performance is a major concern here.
Thank.
Pass data through a void pointer is a good C solution but (IMHO) not a C++ (specially: not a C++11/c++14/C++17, with auto and std::tuple) good one.
So I suggest to return a value from callbackBegin() and pass the value as first argument to `callbackEnd(); something like
auto r = static_cast<Derived*>(this)->callbackBegin(s);
static_cast<Derived*>(this)->callbackEnd(r, s);
Observe (C++11 and newer magic) that using auto as type of the value returned by callbackBegin(), you can return different types from different `callbackBegin().
Bonus suggestion: be more generic in MyArray::push_back(): using variadic templates, there is no need of fix the number and the types of arguments received by callbackBack() and callbackEnd().
Using variadic templates you can modify push_back() as follows
template <typename ... Args>
void push_back (Args const & ... args)
{
auto r = static_cast<Derived*>(this)->callbackBegin(args...);
static_cast<Derived*>(this)->callbackEnd(r, args...);
}
The following is a full working example with two different callback classes (with different number of arguments and different return types)
#include <tuple>
#include <iostream>
template <typename derT>
struct myA
{
template <typename ... Args>
void push_back (Args const & ... args)
{
auto r = static_cast<derT*>(this)->callbackBegin(args...);
static_cast<derT*>(this)->callbackEnd(r, args...);
}
};
struct cb1 : public myA<cb1>
{
int callbackBegin (int s)
{ std::cout << "cb1 b" << std::endl; return s+5; }
void callbackEnd (int r, int s)
{ std::cout << "cb1 e -" << r << ", " << s << std::endl; }
};
struct cb2 : public myA<cb2>
{
std::tuple<std::string, int> callbackBegin (std::string const & name,
int num)
{ std::cout << "cb2 b" << std::endl; return {name+";", num+1}; }
void callbackEnd (std::tuple<std::string, int> const &,
std::string const & name, int num)
{ std::cout << "cb2 e -" << name << ", " << num << std::endl; }
};
int main ()
{
cb1 c1;
c1.push_back(5);
cb2 c2;
c2.push_back("string arg", 7);
return 0;
}
std::any would allow you to hold clock_t (or any other) object and do away with the void* pointers, however that's a C++17 concept and not yet widely available (although there are implementations such as boost::any).
In the meantime, your code may benefit from a little composition over inheritance, as array and callback are conceptually pretty different and don't seem to belong in the same inheritance hierarchy. So, preferring composition, the code might look something like:
template<class T> struct ICallback
{
virtual void callbackBegin(int s, std::unique_ptr<T>& p) = 0;
virtual void callbackEnd(int s, std::unique_ptr<T>& p) = 0;
};
template<class T> class MyArray
{
public:
MyArray(std::shared_ptr<ICallback<T>> cb) { callback = cb; }
void push_back(int s)
{
callback->callbackBegin(s, usrDataPtr);
//do something about array
callback->callbackEnd(s, usrDataPtr);
}
protected:
std::shared_ptr<ICallback<T>> callback;
std::unique_ptr<T> usrDataPtr;
};
class ClockCallback : public ICallback<clock_t>
{
public:
void callbackBegin(int s, std::unique_ptr<clock_t>& c){
c = std::make_unique<clock_t>(clock());
std::cout << "callbackBegin" << std::endl;
}
void callbackEnd(int s, std::unique_ptr<clock_t>& c){
clock_t clock2 = clock();
clock_t different = clock2 - (*c);
std::cout << "callbackEnd time(second)="
<< ((float)different) / CLOCKS_PER_SEC << std::endl;
}
};
int main() {
std::shared_ptr<ClockCallback> c = std::make_shared<ClockCallback>();
MyArray<clock_t> ma(c);
ma.push_back(7);
return 0;
}
You can use a smart pointer to avoid manually deleting your userData
std::unique_ptr<clock_t> userData;
pass it as a reference to your callbacks
void callbackBegin(int s, std::unique_ptr<clock_t> &userData)
and initialize it this way
userData = std::make_unique<clock_t>(clock())
The C++ magic you're asking about is a known as a virtual method. Virtual method is one of the C++ native ways to implement the callback:
class MyArray{
public:
void push_back(int s) {
const auto userData = callbackBegin(s); //# beautiful
//do something about array
callbackEnd(s, userData); //# beautiful
}
private:
virtual clock_t callbackBegin(int) const = 0;
virtual void callbackEnd(int, const clock_t&) const = 0;
};
class Callback : public MyArray{
clock_t callbackBegin(int s) const final {
std::cout<<"callbackBegin"<<std::endl;
return clock(); //# safe
}
void callbackEnd(int s,const clock_t& userData) const final { //#
const auto different = clock() - userDataTyped;
std::cout << "callbackEnd time(second)=";
std::cout << different/CLOCKS_PER_SEC << std::endl;
//# safe
}
};
Another way is to pass two callable objects to the MyArray ctor and using those objects in the push_back method. The callable objects shall store calls to the relevant class Callback methods. Use std::function to implement those callable objects.
I've been a C/C++ developer for about 20 years now, but templates have always been a weak spot for me. With template programming becoming ever more useful, and complicated, in the C++11 and C++14 standards, I decided to try an exercise to learn. I've been moderately successful, but I have an issue I'm having problems with. I have the following class:
namespace Events {
// Place your new EventManager events here
static const uint32_t StatsData = 0;
static const uint32_t StatsRequest = 1;
static const uint32_t StatsReply = 2;
static const uint32_t ApplianceStatsRequest = 3;
static const uint32_t ApplianceStatsReply = 4;
static const uint32_t NullEvent = 5;
};
class EventManager {
public:
static EventManager *instance() {
if (Instance)
return Instance;
return new EventManager();
};
static void destroy() {
delete Instance;
Instance = nullptr;
}
template<typename T>
bool consume_event(uint32_t event, std::function<T> func) {
if (_event_map.find(event) == _event_map.end())
// Create the signal, in true RAII style
_event_map[event] = new boost::signals2::signal<T>();
boost::any_cast<boost::signals2::signal<T> *>(_event_map[event])->connect(func);
return true;
}
void emit(uint32_t event) {
if (_event_map.find(event) == _event_map.end())
return;
try {
boost::signals2::signal<void()> *sig =
boost::any_cast<boost::signals2::signal<void()> *>(_event_map[event]);
(*sig)();
}
catch (boost::bad_any_cast &e) {
SYSLOG(ERROR) << "Caught instance of boost::bad_any_cast: " << e.what();
abort();
}
}
template<typename... Args>
void emit(uint32_t event, Args... args) {
if (_event_map.find(event) == _event_map.end())
return;
try {
boost::signals2::signal<void(Args...)> *sig =
boost::any_cast<boost::signals2::signal<void(Args...)> *>(_event_map[event]);
(*sig)(args...);
}
catch (boost::bad_any_cast &e) {
SYSLOG(ERROR) << "Caught instance of boost::bad_any_cast: " << e.what();
abort();
}
}
private:
EventManager() { Instance = this; };
~EventManager() { Instance = nullptr; };
static EventManager *Instance;
std::map<uint32_t, boost::any> _event_map;
};
This code would potentially go into a large framework that loads multiple modules which are dynamic libraries on linux. The idea would be for a given module to be able to call:
consume_event<ParamTypes><EventNumber, SomeCallack)
The callback may be a function with signature void(ParamTypes), or the result of std::bind() on a function with signature void(ParamTypes).
Another module would then be able to call:
emit<ParamTypes>(EventNumber, ParamValues)
and each module that had called consume_event, would have it's handler called with ParamValues.
This seems to work in almost every case, except when I pass a reference to a custom class, like this:
std::cout << "Sending stats data with ref: " << std::hex << ip_entry.second << std::endl;
emit<ip_stats_t &>(Events::StatsData, *ip_entry.second);
In this case, the function that is connected to the signal, receives 0xa, and promptly crashes when it tries to treat it as an ip_stats_t &.
The output is:
Sending stats data with ref: 0x7fbbc4177d50 <- This is the output of the line seen above
ips addr: 0xa << this is from the function that gets called by the signal.
Update: I just noticed it does the same thing when passing any variable by reference, not just the custom class above.
Additionally, please note that there is no SSCCE in this question because any SSCCE invariable works. The problem does not occur until the working code is put into the above framework.
Update2: The real question here is, how can this design be made better. This one not only doesn't work properly, but syntactically, it stinks. it's ugly, inelegant, and really, there's nothing good about it, except that it did what I wanted it to do and increased my understanding of templates.
Update3: I have now 100% confirmed that this has nothing to do with the data type that I'm passing. If I pass any variable by reference, the slot always receives 0xa as the address of the reference. This includes std::strings, and even ints. If I pass any variable by value, the copy constructor of that value eventually receives 0xa as the reference of the value to copy from. This only happens when calling a slot in module B from a signal created in module A. What am I missing?
Any ideas?
Thanks!
UPDATED I've since come up with a demonstration that would appear to be closer to what you were trying to achieve:
#lk75 For fun, here's an approach that abstracts the event mechanism in a fairly extensible way, while
not being overly complicated
not requiring calling signature to be repeated all over the place (it's in Traits now)
not leaking signals by using true RAII style (SCNR). No more use of new or delete!
See it Live On Coliru.
Note how I simplified the singleton and turned both consume_event and emit into one-liners now:
static EventManager& instance() {
static EventManager instance;
return instance;
};
template <EventId event, typename F>
bool consume_event(F&& func) {
get_slot<event>().connect(std::forward<F>(func));
return true;
}
template <EventId event, typename... Args>
void emit(Args&&... args) {
get_slot<event>()(std::forward<Args>(args)...);
}
Full Code
For reference:
Live On Coliru
#include <boost/any.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/signals2/signal.hpp>
#include <iostream>
#include <memory>
#include <string>
struct ip_stats_t {
std::string canary;
};
enum class EventId : uint32_t {
// Place your new EventManager events here
StatsData = 0,
StatsRequest = 1,
StatsReply = 2,
ApplianceStatsRequest = 3,
ApplianceStatsReply = 4,
NullEvent = 5, // Not implemented
};
namespace Events {
template <EventId> struct Traits;
template <> struct Traits<EventId::StatsData> { using signal_type = boost::signals2::signal<void(int)>; } ;
template <> struct Traits<EventId::StatsRequest> { using signal_type = boost::signals2::signal<void(bool, bool)>; } ;
template <> struct Traits<EventId::StatsReply> { using signal_type = boost::signals2::signal<void(std::string)>; } ;
template <> struct Traits<EventId::ApplianceStatsRequest> { using signal_type = boost::signals2::signal<void(double, ip_stats_t&)>; } ;
//template <> struct Traits<EventId::NullEvent> { using signal_type = boost::signals2::signal<void()>; } ;
template <> struct Traits<EventId::ApplianceStatsReply> : Traits<EventId::ApplianceStatsRequest> { };
}
class EventManager {
public:
static EventManager& instance() {
static EventManager instance;
return instance;
};
template <EventId event, typename F>
bool consume_event(F&& func) {
get_slot<event>().connect(std::forward<F>(func));
return true;
}
template <EventId event, typename... Args>
void emit(Args&&... args) {
get_slot<event>()(std::forward<Args>(args)...);
}
private:
template <EventId event, typename Slot = typename Events::Traits<event>::signal_type, typename SlotPtr = boost::shared_ptr<Slot> >
Slot& get_slot() {
try {
if (_event_map.find(event) == _event_map.end())
_event_map.emplace(event, boost::make_shared<Slot>());
return *boost::any_cast<SlotPtr>(_event_map[event]);
}
catch (boost::bad_any_cast const &e) {
std::cerr << "Caught instance of boost::bad_any_cast: " << e.what() << " on event #" << static_cast<uint32_t>(event) << "\n";
abort();
}
}
EventManager() = default;
std::map<EventId, boost::any> _event_map;
};
int main() {
auto& emgr = EventManager::instance();
emgr.consume_event<EventId::ApplianceStatsRequest>([](double d, ip_stats_t& v) {
std::cout << "d: " << d << ", v.canary: " << v.canary << "\n";
});
emgr.consume_event<EventId::ApplianceStatsRequest>([](double d, ip_stats_t& v) {
std::cout << "And you can register more than one\n";
});
ip_stats_t v { "This is statically checked" };
emgr.emit<EventId::ApplianceStatsRequest>(3.142f, v);
emgr.emit<EventId::StatsData>(42); // no connection, but works
emgr.consume_event<EventId::StatsData>([](int) { std::cout << "Now it's connected\n"; });
emgr.emit<EventId::StatsData>(42); // now with connection!
#if 0
emgr.emit<EventId::ApplianceStatsRequest>(); // error: no match for call to ‘(boost::signals2::signal<void(double, ip_stats_t&)>) ()’
emgr.consume_event<EventId::NullEvent>([]{}); // use of incomplete type Traits<NullEvent>
#endif
}
Old answer:
You seem to have trouble with the variadic forwarding:
(*sig)(std::forward<Args>(args)...);
Also, forwarding really makes sense only when taking the arguments by "universal reference":
template<typename... Args>
void emit(uint32_t event, Args&&... args) { // NOTE!!
However, you do not rely on argument type deduction to get the actual value categories (rvalue vs. lvalue). And, rightly so (because the compiler would likely never get the exact argument types "right" to match the stored signal (making the any_cast fail at best, or invoke Undefined Behaviour at best.)
So in this case, you should dispense with the whole forwarding business:
template<typename... Args> using Sig = boost::signals2::signal<void(Args...)>;
template<typename... Args>
void emit(uint32_t event, Args... args) {
if (_event_map.find(event) == _event_map.end())
return;
try {
Sig<Args...> *sig = boost::any_cast<Sig<Args...> *>(_event_map[event]);
(*sig)(args...);
}
catch (boost::bad_any_cast &e) {
std::cerr << "Caught instance of boost::bad_any_cast: " << e.what();
abort();
}
}
Full demo program: Live On Coliru
#include <boost/any.hpp>
#include <boost/signals2/signal.hpp>
#include <iostream>
#include <string>
struct ip_stats_t {
std::string canary;
};
template<typename... Args> using Sig = boost::signals2::signal<void(Args...)>;
std::map<uint32_t, boost::any> _event_map;
template<typename... Args>
void emit(uint32_t event, Args&&... args) {
if (_event_map.find(event) == _event_map.end())
return;
try {
Sig<Args...> *sig = boost::any_cast<Sig<Args...> *>(_event_map[event]);
(*sig)(std::forward<Args>(args)...);
}
catch (boost::bad_any_cast &e) {
std::cerr << "Caught instance of boost::bad_any_cast: " << e.what();
abort();
}
}
int main()
{
Sig<int, double> sig1;
Sig<ip_stats_t&> sig2;
sig1.connect([](int i, double d) { std::cout << "Sig1 handler: i = " << i << ", d = " << d << "\n"; });
sig2.connect([](ip_stats_t& v) { std::cout << "Sig2 handler: canary = " << v.canary << "\n"; });
_event_map[1] = &sig1;
_event_map[2] = &sig2;
emit<int, double>(1, 42, 3.14);
ip_stats_t instance { "Hello world" }, *ptr = &instance;
emit<ip_stats_t&>(2, *ptr);
}
The following code, which is Sehe's revised code without the boost::signals solved my problem completely. It would appear that boost::signals was having issues passing any data whatsoever across module boundries. Replacing it with a simple vector of functions works in all cases, and is faster anyway!
enum class EventId : uint32_t {
// Place your new EventManager events here
StatsData = 0,
StatsRequest = 1,
StatsReply = 2,
ApplianceStatsRequest = 3,
ApplianceStatsReply = 4,
};
struct ip_stats_t;
namespace Events {
template <EventId> struct Traits;
template <> struct Traits<EventId::StatsData>
{ using signal_vec = std::vector<std::function<void(ip_stats_t &)>>; } ;
template <> struct Traits<EventId::StatsRequest>
{ using signal_vec = std::vector<std::function<void(std::ostream &)>>; } ;
template <> struct Traits<EventId::StatsReply>
{ using signal_vec = std::vector<std::function<void(std::string &)>>; } ;
template <> struct Traits<EventId::ApplianceStatsRequest> :
Traits<EventId::StatsRequest> {};
template <> struct Traits<EventId::ApplianceStatsReply> :
Traits<EventId::StatsReply> {};
}
class EventManager {
public:
static EventManager& instance() {
static EventManager instance;
return instance;
};
template <EventId event, typename F>
void consume_event(F&& func) {
get_slot<event>().push_back(std::forward<F>(func));
}
template <EventId event, typename... Args>
void emit(Args&&... args) {
for (auto &vi : get_slot<event>()) {
vi(std::forward<Args>(args)...);
}
}
private:
template <EventId event,
typename Slot = typename Events::Traits<event>::signal_vec,
typename SlotPtr = std::shared_ptr<Slot>>
Slot& get_slot() {
if (_event_map.find(event) == _event_map.end())
_event_map.emplace(event, std::make_shared<Slot>());
try {
return *boost::any_cast<SlotPtr>(_event_map[event]);
}
catch (boost::bad_any_cast const &e) {
std::cerr << e.what() << " on event #" << static_cast<uint32_t>(event) << "\n";
abort();
}
}
EventManager() = default;
std::map<EventId, boost::any> _event_map;
};
With the changes made in C++11 (such as the inclusion of std::bind), is there a recommended way to implement a simple single-threaded observer pattern without dependence on anything external to the core language or standard library (like boost::signal)?
EDIT
If someone could post some code showing how dependence on boost::signal could be reduced using new language features, that would still be very useful.
I think that bind makes it easier to create slots (cfr. the 'preferred' syntax vs. the 'portable' syntax - that's all going away). The observer management, however, is not becoming less complex.
But as #R. Martinho Fernandes mentions: an std::vector<std::function< r(a1) > > is now easily created without the hassle for an (artificial) 'pure virtual' interface class.
Upon request: an idea on connection management - probably full of bugs, but you'll get the idea:
// note that the Func parameter is something
// like std::function< void(int,int) > or whatever, greatly simplified
// by the C++11 standard
template<typename Func>
struct signal {
typedef int Key; //
Key nextKey;
std::map<Key,Func> connections;
// note that connection management is the same in C++03 or C++11
// (until a better idea arises)
template<typename FuncLike>
Key connect( FuncLike f ) {
Key k=nextKey++;
connections[k]=f;
return k;
}
void disconnect(Key k){
connections.erase(k);
}
// note: variadic template syntax to be reviewed
// (not the main focus of this post)
template<typename Args...>
typename Func::return_value call(Args... args){
// supposing no subcription changes within call:
for(auto &connection: connections){
(*connection.second)(std::forward(...args));
}
}
};
Usage:
signal<function<void(int,int)>> xychanged;
void dump(int x, int y) { cout << x << ", " << y << endl; }
struct XY { int x, y; } xy;
auto dumpkey=xychanged.connect(dump);
auto lambdakey=xychanged.connect([&xy](int x, int y){ xy.x=x; xy.y=y; });
xychanged.call(1,2);
Since you're asking for code, my blog entry Performance of a C++11 Signal System contains a single-file implementation of a fully functional signal system based on C++11 features without further dependencies (albeit single-threaded, which was a performance requirement).
Here is a brief usage example:
Signal<void (std::string, int)> sig2;
sig2() += [] (std::string msg, int d) { /* handler logic */ };
sig2.emit ("string arg", 17);
More examples can be found in this unit test.
I wrote my own light weight Signal/Slot classes which return connection handles. The existing answer's key system is pretty fragile in the face of exceptions. You have to be exceptionally careful about deleting things with an explicit call. I much prefer using RAII for open/close pairs.
One notable lack of support in my library is the ability to get a return value from your calls. I believe boost::signal has methods of calculating the aggregate return values. In practice usually you don't need this and I just find it cluttering, but I may come up with such a return method for fun as an exercise in the future.
One cool thing about my classes is the Slot and SlotRegister classes. SlotRegister provides a public interface which you can safely link to a private Slot. This protects against external objects calling your observer methods. It's simple, but nice encapsulation.
I do not believe my code is thread safe, however.
//"MIT License + do not delete this comment" - M2tM : http://michaelhamilton.com
#ifndef __MV_SIGNAL_H__
#define __MV_SIGNAL_H__
#include <memory>
#include <utility>
#include <functional>
#include <vector>
#include <set>
#include "Utility/scopeGuard.hpp"
namespace MV {
template <typename T>
class Signal {
public:
typedef std::function<T> FunctionType;
typedef std::shared_ptr<Signal<T>> SharedType;
static std::shared_ptr< Signal<T> > make(std::function<T> a_callback){
return std::shared_ptr< Signal<T> >(new Signal<T>(a_callback, ++uniqueId));
}
template <class ...Arg>
void notify(Arg... a_parameters){
if(!isBlocked){
callback(std::forward<Arg>(a_parameters)...);
}
}
template <class ...Arg>
void operator()(Arg... a_parameters){
if(!isBlocked){
callback(std::forward<Arg>(a_parameters)...);
}
}
void block(){
isBlocked = true;
}
void unblock(){
isBlocked = false;
}
bool blocked() const{
return isBlocked;
}
//For sorting and comparison (removal/avoiding duplicates)
bool operator<(const Signal<T>& a_rhs){
return id < a_rhs.id;
}
bool operator>(const Signal<T>& a_rhs){
return id > a_rhs.id;
}
bool operator==(const Signal<T>& a_rhs){
return id == a_rhs.id;
}
bool operator!=(const Signal<T>& a_rhs){
return id != a_rhs.id;
}
private:
Signal(std::function<T> a_callback, long long a_id):
id(a_id),
callback(a_callback),
isBlocked(false){
}
bool isBlocked;
std::function< T > callback;
long long id;
static long long uniqueId;
};
template <typename T>
long long Signal<T>::uniqueId = 0;
template <typename T>
class Slot {
public:
typedef std::function<T> FunctionType;
typedef Signal<T> SignalType;
typedef std::shared_ptr<Signal<T>> SharedSignalType;
//No protection against duplicates.
std::shared_ptr<Signal<T>> connect(std::function<T> a_callback){
if(observerLimit == std::numeric_limits<size_t>::max() || cullDeadObservers() < observerLimit){
auto signal = Signal<T>::make(a_callback);
observers.insert(signal);
return signal;
} else{
return nullptr;
}
}
//Duplicate Signals will not be added. If std::function ever becomes comparable this can all be much safer.
bool connect(std::shared_ptr<Signal<T>> a_value){
if(observerLimit == std::numeric_limits<size_t>::max() || cullDeadObservers() < observerLimit){
observers.insert(a_value);
return true;
}else{
return false;
}
}
void disconnect(std::shared_ptr<Signal<T>> a_value){
if(!inCall){
observers.erase(a_value);
} else{
disconnectQueue.push_back(a_value);
}
}
template <typename ...Arg>
void operator()(Arg... a_parameters){
inCall = true;
SCOPE_EXIT{
inCall = false;
for(auto& i : disconnectQueue){
observers.erase(i);
}
disconnectQueue.clear();
};
for (auto i = observers.begin(); i != observers.end();) {
if (i->expired()) {
observers.erase(i++);
} else {
auto next = i;
++next;
i->lock()->notify(std::forward<Arg>(a_parameters)...);
i = next;
}
}
}
void setObserverLimit(size_t a_newLimit){
observerLimit = a_newLimit;
}
void clearObserverLimit(){
observerLimit = std::numeric_limits<size_t>::max();
}
int getObserverLimit(){
return observerLimit;
}
size_t cullDeadObservers(){
for(auto i = observers.begin(); i != observers.end();) {
if(i->expired()) {
observers.erase(i++);
}
}
return observers.size();
}
private:
std::set< std::weak_ptr< Signal<T> >, std::owner_less<std::weak_ptr<Signal<T>>> > observers;
size_t observerLimit = std::numeric_limits<size_t>::max();
bool inCall = false;
std::vector< std::shared_ptr<Signal<T>> > disconnectQueue;
};
//Can be used as a public SlotRegister member for connecting slots to a private Slot member.
//In this way you won't have to write forwarding connect/disconnect boilerplate for your classes.
template <typename T>
class SlotRegister {
public:
typedef std::function<T> FunctionType;
typedef Signal<T> SignalType;
typedef std::shared_ptr<Signal<T>> SharedSignalType;
SlotRegister(Slot<T> &a_slot) :
slot(a_slot){
}
//no protection against duplicates
std::shared_ptr<Signal<T>> connect(std::function<T> a_callback){
return slot.connect(a_callback);
}
//duplicate shared_ptr's will not be added
bool connect(std::shared_ptr<Signal<T>> a_value){
return slot.connect(a_value);
}
void disconnect(std::shared_ptr<Signal<T>> a_value){
slot.disconnect(a_value);
}
private:
Slot<T> &slot;
};
}
#endif
Supplimental scopeGuard.hpp:
#ifndef _MV_SCOPEGUARD_H_
#define _MV_SCOPEGUARD_H_
//Lifted from Alexandrescu's ScopeGuard11 talk.
namespace MV {
template <typename Fun>
class ScopeGuard {
Fun f_;
bool active_;
public:
ScopeGuard(Fun f)
: f_(std::move(f))
, active_(true) {
}
~ScopeGuard() { if(active_) f_(); }
void dismiss() { active_ = false; }
ScopeGuard() = delete;
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&& rhs)
: f_(std::move(rhs.f_))
, active_(rhs.active_) {
rhs.dismiss();
}
};
template<typename Fun>
ScopeGuard<Fun> scopeGuard(Fun f){
return ScopeGuard<Fun>(std::move(f));
}
namespace ScopeMacroSupport {
enum class ScopeGuardOnExit {};
template <typename Fun>
MV::ScopeGuard<Fun> operator+(ScopeGuardOnExit, Fun&& fn) {
return MV::ScopeGuard<Fun>(std::forward<Fun>(fn));
}
}
#define SCOPE_EXIT \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= MV::ScopeMacroSupport::ScopeGuardOnExit() + [&]()
#define CONCATENATE_IMPL(s1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
#ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __COUNTER__)
#else
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __LINE__)
#endif
}
#endif
An example application making use of my library:
#include <iostream>
#include <string>
#include "signal.hpp"
class Observed {
private:
//Note: This is private to ensure not just anyone can spawn a signal
MV::Slot<void (int)> onChangeSlot;
public:
typedef MV::Slot<void (int)>::SharedSignalType ChangeEventSignal;
//SlotRegister is public, users can hook up signals to onChange with this value.
MV::SlotRegister<void (int)> onChange;
Observed():
onChange(onChangeSlot){ //Here is where the binding occurs
}
void change(int newValue){
onChangeSlot(newValue);
}
};
class Observer{
public:
Observer(std::string a_name, Observed &a_observed){
connection = a_observed.onChange.connect([=](int value){
std::cout << a_name << " caught changed value: " << value << std::endl;
});
}
private:
Observed::ChangeEventSignal connection;
};
int main(){
Observed observed;
Observer observer1("o[1]", observed);
{
Observer observer2("o[2]", observed);
observed.change(1);
}
observed.change(2);
}
Output of the above would be:
o[1] caught changed value: 1
o[2] caught changed value: 1
o[1] caught changed value: 2
As you can see, the slot disconnects dead signals automatically.
Here's what I came up with.
This assumes no need to aggregate results from the listeners of a broadcast signal.
Also, the "slot" or Signal::Listener is the owner of the callback.
This ought to live with the object that your (I'm guessing...) lambda is probably capturing so that when that object goes out of scope, so does the callback, which prevents it from being called anymore.
You could use methods described in other answers as well to store the Listener owner objects in a way you can lookup.
template <typename... FuncArgs>
class Signal
{
using fp = std::function<void(FuncArgs...)>;
std::forward_list<std::weak_ptr<fp> > registeredListeners;
public:
using Listener = std::shared_ptr<fp>;
Listener add(const std::function<void(FuncArgs...)> &cb) {
// passing by address, until copy is made in the Listener as owner.
Listener result(std::make_shared<fp>(cb));
registeredListeners.push_front(result);
return result;
}
void raise(FuncArgs... args) {
registeredListeners.remove_if([&args...](std::weak_ptr<fp> e) -> bool {
if (auto f = e.lock()) {
(*f)(args...);
return false;
}
return true;
});
}
};
usage
Signal<int> bloopChanged;
// ...
Signal<int>::Listener bloopResponse = bloopChanged.add([](int i) { ... });
// or
decltype(bloopChanged)::Listener bloopResponse = ...
// let bloopResponse go out of scope.
// or re-assign it
// or reset the shared_ptr to disconnect it
bloopResponse.reset();
I have made a gist for this too, with a more in-depth example:
https://gist.github.com/johnb003/dbc4a69af8ea8f4771666ce2e383047d
I have had a go at this myself also. My efforts can be found at this gist, which will continue to evolve . . .
https://gist.github.com/4172757
I use a different style, more similar to the change notifications in JUCE than BOOST signals. Connection management is done using some lambda syntax that does some capture by copy. It is working well so far.