0

I have seen the following two examples:

class singleton {
    protected:
        static singleton *instance;
        singleton() { }
    public:
        static singleton *getInstance() {
            if (instance == 0)
                instance = new singleton();
            return instance;
        }
};
singleton *singleton::instance = 0; //This seems weird - why isn't nullptr being used ?

And this example:

class Singleton
{
  private:

    static Singleton *p_inst;
    Singleton();

  public:

    static Singleton * instance()
    {
      if (!p_inst) // why isn't this being compared with nullptr ?
      {
        p_inst = new Singleton();
      }

      return p_inst;
    }
};

To check if the instance has been created, why don't people do something like this:

class Singleton
{
  private:

    static Singleton *p_inst = nullptr;
    Singleton();

  public:

    static Singleton * instance()
    {
      if (p_inst != nullptr) // why isn't this being compared with nullptr ?
      {
        p_inst = new Singleton();
      }

      return p_inst;
    }
};

What is the correct way ?

EDIT: After all the responses below, I'm really confused. What is the definitive way to create a thread-safe singleton, without any bugs.

Rahul Iyer
  • 19,924
  • 21
  • 96
  • 190
  • Regarding the initialization of a pointer to `0`. Before C++11 `nullptr` didn't exist, and the usual "null pointer" was `0`. The code you've seen is probably made before C++11 became common. – Some programmer dude May 03 '18 at 10:00
  • 1
    You might use Meyers' singleton instead: `static Singleton& instance() { static Singleton s; return s; }`. so no nullptr check required. – Jarod42 May 03 '18 at 10:01
  • @Someprogrammerdude so what should I be doing ? – Rahul Iyer May 03 '18 at 10:01
  • 2
    The code might have been written before C++11, and then we didn't have `nullptr`. And all of these are seriously thread unsafe. – Bo Persson May 03 '18 at 10:01
  • 1
    "if (!p_inst) // why isn't this being compared with nullptr ?" this actually is comparing `p_inst` to `nullptr` – 463035818_is_not_an_ai May 03 '18 at 10:01
  • @BoPersson There are many code samples on stack overflow. What is the definitive answer ? – Rahul Iyer May 03 '18 at 10:01
  • @Jarod42 This post: https://stackoverflow.com/questions/13047526/difference-between-singleton-implemention-using-pointer-and-using-static-object says that version may have some nasty bugs. (the correct answer in that post) – Rahul Iyer May 03 '18 at 10:03
  • If you should use `0` or `nullptr` depends on your target: Will you target a C++03 (or earlier) compiler? Or C++11 (or later)? There are still much legacy code out there that is still being maintained using older compilers. – Some programmer dude May 03 '18 at 10:03
  • the only difference between the three ways is that the last one uses in class initialization of the static pointer member, but otherwise they look the same to me – 463035818_is_not_an_ai May 03 '18 at 10:03
  • @Someprogrammerdude C++11 – Rahul Iyer May 03 '18 at 10:03
  • Regarding that answer, it also states that you should avoid the singleton pattern as much as possible. In my opinion even if you need a redesign. And if you really can't avoid it, then which one you should pick depends on your use-cases. – Some programmer dude May 03 '18 at 10:06
  • @KaizerSozay: I won't say that trading possible bugs (with incorrect usage) with order of destruction with memory leak makes the later better (moreover with additional threading issues). – Jarod42 May 03 '18 at 10:10
  • @Jarod42 I'm really confused now. I wish there was a definitive example which everyone could agree on. – Rahul Iyer May 03 '18 at 10:11
  • @Kaizer - If I were to use a singleton I would go with [this example](https://stackoverflow.com/a/271104/597607). But most often, when I need a single object I just create one and use it. – Bo Persson May 03 '18 at 10:16
  • Possible duplicate of [efficient thread-safe singleton in C++](https://stackoverflow.com/questions/2576022/efficient-thread-safe-singleton-in-c) – xskxzr May 03 '18 at 10:44

1 Answers1

1

Note that none of the examples you have listed actually attempts to delete the singleton, and none of them are thread-safe, because they do nothing to synchronise their:

  • compare to null (of various flavours)
  • their allocation of the object
  • their assignment of the pointer

None of your existing methods are able to safely test if the singleton has yet been created.

C++11 adds a guarantee for the method's static data member that its initialisation will be thread-safe - i.e. only called once. Tghis is the recommended mechanism. The classic singleton is something like:

SClass& Singleton()
{ 
  static SClass single(args);
  return single;
}

If you really want the object to be heap allocated:

SClass* Singleton()
{ 
  static std::unique_ptr<SClass> single (new SClass(args));
  return single;
}

[ Note that you can use function calls and/or lambdas in the construction, if your object construction isn't a simple new. You can write lambdas as the second argument of the std::unique_ptr to get complex destruction, so you can easily customise within this model. ]

If you really want to do it without using a static member, you could use a std::mutex or a std::atomic with cmpexchange, but in c++11 the static member does that work for you.

Also the object will be destroyed, and in reverse order to the order they were created. However, that can lead to phoenix issues, where SomeThing calls on the singleton during it's destruction, and it was constructed earlier than your singleton, so gets destructed later.

The versions you are looking at simply never delete the object, which will make valgrind very unhappy. If you want your existing behaviour, with a thread-safe construction, but no destruction, use a raw pointer (they don't have destructors):

SClass* Singleton()
{ 
  static SClass* single = new SClass(args);
  return single;
}

The retroactive fix is to have that SomeThing call on the singleton during its construction, or be able to handle a nullptr on return. Of course you have to debug that scenario before you can fix it. So I recommend you at least add an abend to your singleton so that you know why it is failing: [Actually, as flagged in the comments this is not so trivial, as unique_ptr might not leave itself in a good state after destruction, so we need a custom ptr lifetime handler. Luckily, we don't really NEED a full-blown unique_ptr here, just 3 or 4 methods. Potentially this walks into UB, as the compiler could optimise away this null assignment during destruction, or DEADBEEF this location, but phoenixes should not be relied on in production code anyway.]

SClass* Singleton()
{ 
  struct PUP: std::unique_ptr<SClass> single
  {
    typedef std::unique_ptr<SClass> base;
    PUP(SClass* s): base(s) {}
    ~PUP()  { operator =( base()_); }
  };
  static PUP single (new SClass(args));
  assert(single && "Singleton has been called in late static destructor - need phoenix");
  return single;
}

You can, if you like, have the object rise from the dead. This should be safe to do as static destruction is single-threaded. Note that the re-arissen object can never be destroyed, and does not keep any of the state of the old object. This can be useful for logging classes, where someone adds logging in a destructor not realising the logging class singleton has already been destroyed. For a diagnostic, this is useful. For production code, it will still upset valgrind, just don't do it!

SClass* Singleton()
{ 
  static PUP single = new SClass(args);
  if (!single)
  {
    single = std::unique_ptr<SClass>(new SClass(args));
    // Hmm, and here is another phoenix being revived:
    Logger(CRITICAL) << " Revived singleton for class SClass!" << std::endl;
  }
  return single;
}
Gem Taylor
  • 5,381
  • 1
  • 9
  • 27
  • 1
    thats pretty confusing – Rahul Iyer May 03 '18 at 10:27
  • Singletons are not trivial to get right. I know the posting is long, but I hope the description helps you to understand the options you have. – Gem Taylor May 03 '18 at 10:48
  • I'm avoiding them - there are many posts on singletons, but all of them seem to have multiple differing opinions. – Rahul Iyer May 03 '18 at 10:50
  • OK well thank you for appreciating my effort, anyway. As you say there are multiple opinions. That is because there are a number of classic pitfalls, and it is a non-trivial problem. There is no definitive answer to fix all the possible issues. Come back with a question that is answerable. – Gem Taylor May 03 '18 at 14:09
  • This isn't a problem in languages like Java - I don't think the question is unanswerable - it's just that c++ is different, and no one seems to have come up with a definitive answer yet. – Rahul Iyer May 03 '18 at 14:11
  • That would be because of the automatic garbage collection. So go program in c#. It is like C but without these problems. – Gem Taylor May 03 '18 at 14:24
  • Why would you think that `up.~unique_ptr()` will result in `!up` -- setting the pointer to null can easily be determined to have no observable side effects and thus can be eliminated. Accessing the destroyed object is, after all, UB. – Yakk - Adam Nevraumont May 03 '18 at 15:53
  • @Yakk. Good spot. My only excuse is it seems to work for me :-) [or perhaps that's why I'm not actually using an out-the-box unique_ptr here!] That suggests probably a custom destructor mechanism will be needed to ensure the pointer IS cleared on destruction, as well as on null assignment.. – Gem Taylor May 03 '18 at 16:29
  • @GemTaylor After an object is destroyed, using it is UB and examining the state of the memory gives an unspecified result. If the last thing your destructor does is set some memory to zero (say a pointer), the compiler is perfectly justified in eliminating that memory write as having no observable effects in the abstract machine. You *cannot* examine a destroyed object and get anything reliable out of it. – Yakk - Adam Nevraumont May 03 '18 at 17:51
  • In which case you are sunk :-) since you can keep saying "the compiler is permitted to ignore you 'cos it thinks it knows it is destroying" all the way to the heat death of the universe. Theclassic way out of this quandry is to have a second static that has no destructor, but even then, you could equally say that the compiler just might fill that at the point it "goes out of static scope" with DEADBEEF. I recon phoenixes are black art enough that you keep your fingers crossed. – Gem Taylor May 04 '18 at 10:38