3

I'm trying to find a way to achieve abstraction with the lowest possible overhead.

This is for an embedded system (Cortex-M4), I use the latest gcc compiler arm-none-eabi.

Let's say we have a pure abstract classes containing only pure virtual functions.

class Animal
{
    virtual void sound() = 0;
    virtual void eat() = 0;
    virtual void sleep() = 0;
}

We can inherit the class above and implement/overload its functions.

I thought another way to do that. Using cmake I can have selective compilation. I can have the standard declaration of a class in a header file and using cmake I can choose the implementation I want by adding my *.cpp file. Note that I will only need one implementation file *.cpp for each software version... So, in that way I can get rid of the overhead that virtual functions add.

7
  • if a pointer dereference is too expensive you will have to make it a concrete class. In that case your CMake solution should work. May 12, 2022 at 6:43
  • @JerryJeremiah Is that only a pointer dereference? May 12, 2022 at 6:51
  • Typical implementations of virtual functions need "one more" pointer dereference each call, but the as-if rule can improve that
    – Caleth
    May 12, 2022 at 8:47
  • Yes. You can. What is the question?
    – user253751
    May 12, 2022 at 10:01
  • If you only have one derived class, and it's OK for everyone to know what it is, why do you need a base class at all? May 12, 2022 at 13:19

1 Answer 1

3

That's good if you need just one implementation in a given binary

In particular for differences based on the target device, since one expects a given binary to be running on a particular platform.

I have found it helpful to further organize the differences into subdirectories, so that most of the project can refer to the files unqualified, with the build system setting the search paths appropriately for each build configuration. That also makes it easier to keep track of which files are generic and which are tied to a particular implementation.

For example, if your cortex-m4 uses the lwip BSD socket api, and you want to debug the code using the sockets on desktop linux or windows, you might have a platform/lwip, platform/bsd, and/or platform/winsock subdirectories. Then files for a "socket_interface" that provide a common API to the rest of your code, adapting to whichever actual socket implementation they include, go in those directories. Your build configuration can set the search directories (and libraries to link) appropriately for each target. I once ported an existing library from Windows (winsock, winthread) to a cortex-M4 (lwip, CMSIS/KEIL), and handled the different implementations that way, so I could still debug the common code--pretty much everything besides the socket and thread code--on Windows with wireshark to record packets.

Since a given binary would only ever target one platform, and thus one socket implementation, anyway, there isn't much drawback to letting the build configuration switch such implementations. I don't know exactly what your real-world situation is, so I'll note that this kind of method can get unwieldy once you start needing more than one implementation in the same binary.

There is an alternative if you need more than one implementation

If you need multiple implementations within a given build, but still want to avoid a vtable lookup, one alternative is to consider making your class a template instead, for example using CRTP to get compile-time polymorphism, rather than run-time polymorphism. Do be aware that in this method, base pointers to different derived classes are not convertible as they would be with run-time polymorphism. For derived classes "A" and "B", and base template "base<typename>", the base classes are "base<A>" and "base<B>", which are, to the language, completely different types, even if they look basically the same to us.

If you would benefit from run-time polymorphism, use it anyway

It sounds like you don't actually need run-time polymorphism in this particular situation, in which case, absolutely don't use it. The general C/C++ rule of "don't pay for it if you don't need it" especially applies to embedded devices; saving resources by eliminating things you didn't need can mean more capacity for other features, or downgrading to cheaper hardware, which can in turn mean competitive advantage.

That said, the Cortex-M4 may be more powerful than you might think. If you find yourself in a situation where inheritance provides a suitable benefit, I'd use inheritance anyway.

As an anecdotal data point from my own personal experience, the right model M4 with the right supporting hardware and appropriate configuration, can run a preemptive RTOS, filter digital and analog user control inputs, handle Ethernet over EtherCAT (including rerouting packets) fast enough for remote display (800x480 pixel) for an industrial HMI. That project was in C, not C++, so there were no virtual functions, but my point is rather that this isn't a chip with a data bus width too small for pointers, or a chip that can't handle the overhead of a preemptive RTOS, or, depending on model and use case, a chip that would even need slower off-chip RAM.

Do make sure you review gcc's options to tune for the cortex-M4, but for judicious use of single, non-virtual inheritance, I wouldn't be especially worried unless and until I saw the code running too slowly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.