How to save get_instance call on Singleton in C++?

Viewed 452

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?

3 Answers

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.

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;

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

E.g.

Camel& camel = Camel::get_instance();
std::cout << camel.answer();
Related