HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Delegate and Observer pattern implementation for an embedded system

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
embeddedobserversystemdelegateforandimplementationpattern

Problem

I'm writing a set of simple widgets for a microcontroller application. I'd like to use the observer pattern to pass around events: when a button is clicked, when a timeout occurs - there are many uses which I'm currently implementing with very ugly glue code, with similar code occuring again and again.

The whole system is more or less static, i.e. widgets are created on startup, not dynamically. I'd like to get away with as little heap usage as possible, just to be able to tell (at compile time) if I'm running out of RAM (16 kB). This is the reason why I've not implemented any delegate destructor code. It would never be used while the system is running.

When an event occurs, a Signal is fired, calling all connected observers. These are delegates that call the actual free function or method on some object. Here's my current code:

#include 

#include 
#include 

// Interface for delegates with the same set of arguments
template
class AbstractDelegate
{
  public:
    virtual void operator()(args...) = 0;
};


There are two concrete Delegate classes. One for non-static member functions and one for free functions:

// Concrete member function delegate that discards the function's return value
template
class ObjDelegate : public AbstractDelegate
{
  public:
    typedef ReturnType (T::*ObjMemFn)(args...);
    ObjDelegate(T& obj, ObjMemFn memFn)
      : obj_(obj),
      memFn_(memFn)
    {
    }
    void operator()(args... a)
    {
      (obj_.*memFn_)(a...);
    }
  private:
    T& obj_;
    ObjMemFn memFn_;
};

// Concrete function delegate that discards the function's return value
template
class FnDelegate : public AbstractDelegate
{
  public:
    FnDelegate(ReturnType (*fn)(args...))
      : fn_(fn)
    {
    }
    void operator()(args... a)
    {
      (*fn_)(a...);
    }
  private:
    ReturnType (*fn_)(args...);
};


Helper functions for Delegate construction:

```
// create a delegate, returned object can be auto'ed
template
ObjDelegate make_deleg

Solution

Some tidbits about using C++11 features efficiently:

-
I find that the old typedef is rather hard to read for function pointers. You could refactor this line:

typedef ReturnType (T::*ObjMemFn)(args...);


into this one using a C++11 type alias:

using ObjMemFn = ReturnType (T::*)(args...);


-
You can use curly braces instead of regular parenthesis to initilize the member variables in your constructor initialization lists. That will prevent implicit narrowing type conversions:

ObjDelegate(T& obj, ObjMemFn memFn)
  : obj_{obj},
    memFn_{memFn}
{}


Be careful though, it does not work with some types, like reference types for example.

-
you can also use the new override specifier to explicitly tell the compiler that you do want to override a base class function. For example, ObjDelegate::operator() could be declared as:

void operator()(args... a) override
{
    // ...
}


While it is not necessary, it may help you to catch some subtle errors when the signature of the function you want to override in the base class is slightly different from the one you wrote in the derived class.

-
You can use list initialization to reduce the boilerplate in make_delegate: using a pair of curly braces in a return statement constructs and returns a value of the same type as the declared return type:

template
FnDelegate make_delegate(ReturnType(*Fn)(args... a))
{
    return { Fn };
}


-
Don't hesitate to use auto to deduce types instead of writing overly long types. Some same that the idiom should be "Almost Always auto". For example, instead of:

typename std::list::const_iterator i = listeners_.begin();


write:

auto i = listeners_.cbegin();


Note that I used cbegin instead of begin to make sure that the deduced type would be const. If two overload of a function are available (const and non-const), the one without the const specifier will be choosed by the compiler.

-
And the usual one, that I almost forgot (because it's not C++11 specific): you do not need to write return 0; at the end of your main. If your main ends without having encountered a return statement, the compiler will automagically add return 0; at the end of main for you :)

Code Snippets

typedef ReturnType (T::*ObjMemFn)(args...);
using ObjMemFn = ReturnType (T::*)(args...);
ObjDelegate(T& obj, ObjMemFn memFn)
  : obj_{obj},
    memFn_{memFn}
{}
void operator()(args... a) override
{
    // ...
}
template<typename ReturnType, typename... args>
FnDelegate<ReturnType, args...> make_delegate(ReturnType(*Fn)(args... a))
{
    return { Fn };
}

Context

StackExchange Code Review Q#47098, answer score: 6

Revisions (0)

No revisions yet.