game-programming-patterns icon indicating copy to clipboard operation
game-programming-patterns copied to clipboard

Meyer's Singleton in Singleton pattern

Open jpgaribotti opened this issue 9 years ago • 2 comments

I appreciate that you mention the new "static initialization is thread safe" feature in the Singleton pattern, but you didn't go far enough to make the code modern.

Your example looks like this:

class FileSystem
{
public:
  static FileSystem& instance()
  {
    static FileSystem *instance = new FileSystem();
    return *instance;
  }

private:
  FileSystem() {}
};

It can be further simplified to just:

class FileSystem
{
public:
  static FileSystem& instance()
  {
    static FileSystem instance;
    return instance;
  }

private:
  FileSystem() {}
};

The static local variable will only be initialized once, the first time instance() is called, and will have a well defined lifetime to boot. Every call will return the same instance, too, because it is static.

Heh, reminds me of a joke a guy said, that C++11 was a disappointment because they didn't find new uses for static.

jpgaribotti avatar Jan 04 '15 18:01 jpgaribotti

So by declaring instance like that you do the same thing but use a kind of syntax sugar? Or does it behave differently indeed?

mvcds avatar Jan 07 '15 14:01 mvcds

It behaves differently (and, in my opinion, more correctly).

There are two main differences. One, it gets static allocation instead of getting allocated in the heap at runtime. That's a small win.

Two, it has a well defined lifetime. A heap allocated object won't have it's destructor called when the program terminates. Statically allocated objects will. You can test this yourself with a simple test program:

#include <iostream>

class TestClass
{
    public:
        static TestClass& instance1(){ static TestClass instance;}
        static TestClass& instance2(){ static TestClass* instance = new TestClass();}
        ~TestClass(){ std::cout << "TestClass::~TestClass()\n";}
    private:
        TestClass(){ std::cout << "TestClass::TestClass()\n";}
}

int main()
{
    std::cout << "main()\n";
    auto& testInstance = TestClass::instance1();
    return 0;
}

The output of this program will be:

main()
TestClass::TestClass()
TestClass::~TestClass()

If you change the call in main from TestClass::instance1() to TestClass::instance2(), however, you'll see that the call to the destructor doesn't happen.

Most of the time it doesn't matter, the OS will clean up any resources. But if your singleton is managing a resource on another computer, for example a connection to a server in a multiplayer game, you won't do the required cleanup when you exit, such as sending a disconnect message.

This can be the difference between other players seeing you drop from a game immediately, or having their game paused for a minute while the server tries to reconnect before finally dropping you.

And from a correctness perspective, your program should call a destructor for every object it constructed, regardless of whether the OS or someone else knows how to clean up your mess.

jpgaribotti avatar Jan 07 '15 15:01 jpgaribotti