Compizfox
Compizfox

Reputation: 748

Is it possible to create an interface without using dynamic dispatch?

Background

I'm writing the firmware for an embedded project with a couple of 'choices'. For example, the device uses a humidity sensor, but it supports multiple different humidity sensors.

To implement this nicely, I have written an (abstract) base class as an interface to represent the sensors:

class HumiditySensor {
public:
    virtual void readSample() = 0;
    virtual double getHumidity() const = 0;
    virtual double getTemperature() const = 0;
};

Now, different specific sensors can inherit from this base class and implement the pure virtual methods.

class SHTHumiditySensor : public HumiditySensor {
private:
    SHTSensor &sht;
public:
    explicit SHTHumiditySensor(SHTSensor *sht);
    void readSample() override;
    double getHumidity() const override;
    double getTemperature() const override;
};

class DHTHumiditySensor : public HumiditySensor {
private:
    DHT &dht;
public:
    explicit DHTHumiditySensor(DHT *dht);
    void readSample() override;
    double getHumidity() const override;
    double getTemperature() const override;
};

In other parts of my code, I can just 'ask for' a (pointer to) HumiditySensor and not care about what kind of sensor it is, or how it is implemented, because they expose the same common interface. E.g. in a class that needs a humidity sensor:

class Humidistat {
private:
    HumiditySensor &hs;
public:
    Humidistat(HumiditySensor *hs);
}

Based on a config constant, in main.cpp I'll either instantiate either of the two sensors, and pass a pointer to it in the instantiation of a Humidistat.

I have a few more cases like this in my project (multiple possible UIs for different displays, etc).

Problem/question

This is all fine and well, but as far as I understand this all takes quite a bit of precious flash/RAM on my microcontroller because this uses dynamic dispatch / run-time polymorphism.

However, which of the two derived classes I'm going to use is in principle fixed and known at compile-time. It would therefore be nice to avoid the overhead of run-time polymorphism, but is this possible?

Thus, if I understand correctly, what I want is static (or compile-time) polymorphism. I have found that this can be accomplished with the Curiously Recurring Template Pattern (CRTP), but it looks quite convoluted/hacky and I'm wondering if that's really the simplest way to achieve what I want.

Upvotes: 1

Views: 492

Answers (2)

n. m. could be an AI
n. m. could be an AI

Reputation: 120079

You have only one class, so there is no polymorphism by definition. You don't need inheritance, interfaces, CRTP, or any such hacks.

class SHTHumiditySensor /* no ': public HumiditySensor' needed */ {
private:
    SHTSensor &sht;
public:
    explicit SHTHumiditySensor(SHTSensor *sht);
    void readSample() /* no virtual, no override */;
    double getHumidity() const /* no virtual, no override */;
    double getTemperature() const /* no virtual, no override */;
};

using HumiditySensor = SHTHumiditySensor;

Upvotes: 2

eerorika
eerorika

Reputation: 238441

If you want to avoid the cost of runtime polymorphism, then you must rely on compile time polymorphism instead. The tool for compile time polymorphism is templates.

Yes, templates can be harder to use; compile timeness limits what can be done. CRTP that you mention is a useful technique in some use cases, but I don't see a need for it in the shown example.

In the most simple case, you could use templates such as this:

template <class Sensor>
class HumiditySensor {
   Sensor &sht;
   // ...
};

// If needed:
// using SHTHumiditySensor = HumiditySensor<SHTSensor>;
// using DHTHumiditySensor = HumiditySensor<DHT>;

template <class Sensor>
class Humidistat {
    HumiditySensor<Sensor> &hs;
    // ...
}

To specify how to define a Sensor class usable in such template, you simply specify a concept of a class that has certain members. This is largely the way the STL is defined. Previously, such concepts have been written documentations, but in C++20 there is now a way to define them programmatically - that's largely optional but can have some benefits.

Upvotes: 3

Related Questions