2

I'm converting a C codebase to C++ for an embedded device (ESP32). One thing that's repeating in the current code is a way of declaring "callbacks" to C modules and implement them in the "user" module.

For example, the current API for a Button module is the following:

void btn_init(uint32_t gpio);

// callbacks
extern bool btn_on_change_isr(bool is_pressed, uint32_t timer_duration_s);
extern bool btn_on_timer_isr(uint32_t duration_s);

Knowing that functions are externed by default, this is simply a mark that the module is not the one responsible to implement them. The main advantage of this method is that unimplemented callbacks are catched at compile time by the linker.

While transitioning to C++, I'd like to keep that compile-time check for existance of callback implementation (I don't need dynamic dispatch), but also allow creating multiple objects, so I need classes.

While I'm new to CRTP & Policy-based-design, I guess I should go with one of these. Or maybe template functors? What is the difference between these two?

My current idea is the following:

template <typename Impl>
class Button
{
  public:
    Button(uint32_t gpio, Impl impl_ = Impl())
        : impl(impl_)
    {
        (void)gpio;
    }
    bool on_change_isr(bool is_pressed, uint32_t timer_duration_s)
    {
        return impl.on_change_isr(is_pressed, timer_duration_s);
    }
    bool on_timer_isr(uint32_t duration_s)
    {
        return impl.on_timer_isr(duration_s);
    }

  private:
    Impl impl;
};

Where the implementing code is:

class MyButton
{
public:
    bool on_change_isr(bool is_pressed, uint32_t timer_duration_s)
    {
       // implementation 
    }
    bool on_timer_isr(uint32_t duration_s)
    {
       // implementation 
    }
};

One main thing that bugs me here is that I'd still like to keep the implementation in a cpp file rather then the header. For the Button class, I'd like to implement the constructor in a cpp file. How can I achieve that?

Overall, does this makes sense? Is there a better way to injecting callbacks without using inheritance?

galah92
  • 3,621
  • 2
  • 29
  • 55
  • You can define your template class `Button` methods in a `cpp` file, but you'll either need to include the `cpp` from the header file, or instantiate `template class Button;` in the `cpp` file in order for the code to be compiled. A template class definition alone does not generate any code, only an instantiation does. – Patrick Roberts Feb 24 '21 at 08:17
  • Dynamic dispatch should be ok though (seems to be ui related triggered by user, so "overhead" of that dispatch is unimportant). – Jarod42 Feb 24 '21 at 08:47
  • I do not understand, how does `btn_on_change_isr` know which gpio was pressed? `I'm new to CRTP & Policy-based-design` But... why? Why use advanced stuff? Why not make your interface just plain simple and basic? `What is the difference between these two?` Well, this forum prefers one question per post. Between which "two"? `'d like to implement the constructor in a cpp file. How can I achieve that?` Like for any other class? Maybe you should start with a good C++ book instead that explains basic stuff, like out-of-class methods definitions? – KamilCuk Feb 24 '21 at 09:33
  • That `class Button` does nothing... You could write another, `class SuperButton` that takes a `Button` and does the same - calls `Button` methods... It looks like an abstraction where no abstraction is needed - just use the `impl` classs by itself, why use a wrapper? – KamilCuk Feb 24 '21 at 09:38
  • @KamilCuk `Button` invokes `on_change_isr()` when needed (that's implementation details of the class. The right GPIO is passed in the constructor. The implementation of `on_change_isr()` can be different for different buttons (instances). Interfaces (dynamic dispatch) is unwanted because it is uneeded. Also there's no GUI involved - this class is for an actual HW button. – galah92 Feb 24 '21 at 12:04

0 Answers0