3

In this dummy example below, I would like to create a Singleton where I can save the get_instance call which is costly on embedded microcontrollers.

#include <stdio.h>

template<class T>
class CamelBase {
    public:
    static T& get_instance() {
        static T instance;
        return instance;
    }    

    protected:
    CamelBase() {}    
    CamelBase(T const&) = delete;
    void operator=(T const&) = delete;
};

class Camel: public CamelBase<Camel> {
    public:
    int answer() {
        return 42;
    }
};

int main(void)
{
    printf("%d\n", Camel::get_instance().answer());
    return Camel::get_instance().answer();
}

We can see here https://godbolt.org/g/1ugPxx that each call to answer calls the get_instance, which is weird because the compiler inlined answer anyway.

main:
  push {r4, lr}
  bl CamelBase<Camel>::get_instance()
  mov r1, #42
  ldr r0, .L15
  bl printf
  bl CamelBase<Camel>::get_instance()
  pop {r4, lr}
  mov r0, #42
  bx lr

Is there another way to write that kind of Singleton for peripherals such as I2C or SPI?

Is it better to use a static class? A reference pointer?

nowox
  • 25,978
  • 39
  • 143
  • 293
  • Why would I2C or SPI be a singleton in the first place? Many MCU support several SPI peripherals on the same chip. It would rather seem that you are looking for a way to associate one bloated meta programming template object with one particular hardware peripheral. How to do this in the best way with template meta programming, we can ponder for a couple of weeks. Or... we could just write the actual driver. Is it actually written in the specification that the product must be able to swap between any known serial bus interface, at any time? – Lundin Nov 07 '17 at 15:33

3 Answers3

3

Every time you write a function static variable, that translates to a conditional: if it's not yet initialized, it will be. Your compiler probably cannot optimize globally, thus it doesn't know if the instance is there already, hence the call.

The first question you should ask yourself: is it really a singleton? Singleton pattern documents that something will break beyond recoverable if you have multiple instances (even while testing). Is it your case? If not and you just want a common instance, use a static variable (unless you need a specific destruction order). Remember, singleton documents a design flaw in your code, as opposed to being a feature.

If you really need a singleton, what we usually have is (as opposed to the above, which is Scott Meyers singleton) a static private member pointer to the instance (defaulted to nullptr) and an explicit if in get_instance() to populate it if needed. Your compiler might or might not optimize this better than the other - usually it won't check the condition twice in the same fn and get_instance() will be inlined.

If this doesn't help, you can still have a reference per function to the singleton, say, auto&& instance = CamelBase<Camel>::get_instance(). But then, you could take it as a function argument as well and then the problem goes up, way up to main - where you just create an instance. This doesn't work if you wanted on-demand instances.

lorro
  • 10,687
  • 23
  • 36
1

As far as I see, construction of static T instance prevents inlining. If you use something like:

private:
    static T inst;
public:
static T& get_instance() {
    static T *instance = &inst;
    return *instance;
}    

Then get_instance() is inlined;

gpeche
  • 21,974
  • 5
  • 38
  • 51
  • you can just private: static T instance; public: static T& get_instance() { return instance; } and get inlined too – OznOg Nov 06 '17 at 21:22
  • @OznOg yes, but as it is I think it shows better the difference between pointer initialization (= cheap) and generic initialization (= can be costly) – gpeche Nov 06 '17 at 21:27
  • I mean you don't need the `static T*` inside your function; works the same without this (useless) variable as the important thing was to move the object instance outside the fucntion – OznOg Nov 06 '17 at 21:29
  • @OznOg I understand you, and would do what you suggest in production code. But then you don't answer the (imho interesting). question 'why doesn' t the function get inlined?' – gpeche Nov 06 '17 at 21:32
0

You can have a reference to the instance, in a separate variable.

E.g.

Camel& camel = Camel::get_instance();
std::cout << camel.answer();
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621