Overview
Long story short. I've implemented a self written framework for Stm32F7 and Stm32F4 in order to learn C++. On my first draft I heavily relied on virtual functions but in a recent task I got performance issues using that design (microsecond timing relevant task).
So I tryed to use static polymorphism but I'm not sure if that's a good solution. I'll explain my issues in the examples provided.
Overview:
I have an interface for GPIO functions. In this reduced example just write
Then there are different MCU specific implementations of this interface. I just provided the F7 Example but others should be possible.
And lastly I have Domain specific code. In this example some Sensor that fetches uses a GPIO internally in order to fetch someting.
Important: The Sensor should not rely on the specific hardware. It should rely on the abstraction of GPIO provided.
Solutions I've found other than virtual functions
1: CRTP
// gpio interface
namespace V1 {
template<class D>
class Gpio
{
public:
void write(bool status) {
static_cast<D*>(this)->impl_write(status);
};
};
}
// f7 specific gpio
namespace V1 {
class F7 : public V1::Gpio<F7> {
public:
void impl_write(bool status) {
}
};
}
// sensor
namespace V1 {
template <class GPIO_T>
class Sensor {
private:
Gpio<GPIO_T> *gpio;
public:
Sensor(Gpio<GPIO_T> *gpio) : gpio(gpio) {}
void read() {
return gpio->write(true);
}
};
}
// main
using gpio_f7 = V1::F7;
V1::Gpio<gpio_f7> gpio {};
V1::Sensor<gpio_f7> sensor {&gpio};
This code is actually working and also removing the call overhead from virtual functions in compile time. But it has quite some drawbacks.
As I rely on template parameters everything has to be header only.
I can't store the abstraction Gpio *gpio
as the base (V1::Gpio
) needs the deriviate as template, so it has to be Gpio<GPIO_T> *gpio
. The issue with that is that this results in EVERY CLASS that uses my abstractions to be templetized (like GPIO_T
above). Which for me feels like a huge deficit. But as I'm not in C++ industry maybe this is completly fine ?
2: Template functions
The other option I've found is a C-Style template functions api.
// interface
namespace V2 {
template<class T>
void write(T& gpio, bool status) {
gpio->write(status);
}
}
// gpio
namespace V2 {
class F7 {
public:
void write(bool status) {
}
};
}
// sensor
namespace V2 {
class Sensor {
private:
F7 *gpio;
public:
Sensor(F7 *gpio) : gpio(gpio) {
}
void read() {
return write(gpio, true);
}
};
}
// main
V2::F7 gpio {};
V2::Sensor sensor {&gpio};
IMO the C-Style API is for me a big disadvantage. As this hides the functions its easier to see which functions are available on an object using an IDE. (Maybe functions are hidden behind different include files)
furthermore I cant store the Abstraction the the Sensor anymore and have to directly annotate the F7 classes. Therefore I would have to change EVERY class if I want to use a different MCU.
So I'm actually not really happy with both solutions. Atm I'm tending to rewrite my classes in CRTP-style. As at least for the most important most used classes like GPIO I would rather have the most performance I can archieve.
So my questions are:
Am I using CRTP wrong ? Is there a way to get rid of templetizing every class that uses the abstractions ? Or is it okay (idiomatic?) to templetize every class ? Are there other solutions (C++ Version doesn't matter can be C++20)?