9
#include <iostream>
#include <memory>

using namespace std;

class Demo {
    static shared_ptr<Demo> d;
    Demo(){}
public:    
    static shared_ptr<Demo> getInstance(){
        if(!d)
        d.reset(new Demo);
        return d;
    }
    ~Demo(){
        cout << "Object Destroyed " << endl;
    }

};

//    shared_ptr<Demo> Demo::d(new Demo); // private ctor is accepted 

shared_ptr<Demo> Demo::d;

int main()
{
    shared_ptr<Demo> d(Demo::getInstance());
    cout << d.use_count() << endl;

   return 0;
}
  1. is this the correct way to implement the singleton class using shared_ptr
  2. please see above commented line to initialize the static shared_ptr how come we can create an object here to initialize shared_ptr with a private construct
Jagan Arikuti
  • 365
  • 1
  • 2
  • 11
  • 2
    Singletons are more of an anti-pattern, so I would recommend to not implement them at all. They are not that different from global variables. – Jens Oct 28 '15 at 18:58
  • 8
    While many agree that singletons, used unwisely, can be problematic, I don't think it is particularly useful here to criticize them -- it doesn't help the questioner solve a problem. – David J. Aug 31 '17 at 02:14

3 Answers3

19

This is not thread-safe: two threads calling getInstance would cause a data race. A common approach is to use a function-scope static variable:

static shared_ptr<Demo> getInstance(){
  static shared_ptr<Demo> d(new Demo);
  return d;
}

Such a variable is guaranteed to be initialized exactly once, when control passes over its definition for the first time, and in a thread-safe manner.

At this point though, it's not at all clear why you would want to use shared_ptr. You could just as well do

static Demo& getInstance(){
  static Demo d;
  return d;
}

This is a textbook implementation of a singleton (well, one of).


Re: initialize with a private constructor. I'm not sure I understand the nature of your confusion. Are you asking why Demo::getInstance can use private constructor of Demo? Well, because it's a member of Demo, and members of a class can access private members of that class. Are you asking why Demo::getInstance can call shared_ptr<Demo>::reset() passing a Demo* pointer? Well, because reset() is a public member function of shared_ptr, taking a pointer as a parameter. Which part of this process do you find controversial?

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • static variable of getInstance() does not work , I think – Jagan Arikuti Oct 28 '15 at 18:23
  • @IgorTandetnik, I tested your getInstance() in a multi-threaded tight loop and it works very well! But can you please explain what is going on? Why is 'Demo' only instantiated a single time? No checking for nullptr, what kind of magic is this? – Caroline Beltran Aug 31 '18 at 05:19
  • 2
    @CarolineBeltran Funny you should say that. This feature of the C++ language is often referred to as ["magic statics"](http://blog.mbedded.ninja/programming/languages/c-plus-plus/magic-statics). The compiler automatically generates code that performs the necessary synchronization. – Igor Tandetnik Aug 31 '18 at 13:10
  • @IgorTandetnik, much clearer now, thank you. I currently pass a shared pointer to a single database pool instance in an Asio async app. It works very well but it is just plain messy looking. It would be so much cleaner to use a singleton but there is so much historical negative info regarding multi-threaded/singleton apps. On my tight loops (hundreds of millions of iterations), I experienced zero problems but I would like to know if YOU would trust a singleton db instance in your app, especially with the c++ 11 thread safe static initialization guarantee. Thanks again. – Caroline Beltran Aug 31 '18 at 15:10
  • 1
    @CarolineBeltran I do use singleton instances in my project. None of them happen to manage a database connection, but I don't see why that would make a difference. – Igor Tandetnik Aug 31 '18 at 17:18
  • I understand this is an example using shared_ptr. But it is the **same** shared_ptr that is returned every time GetInstance() is called. Like @IgorTandetnik, I am confused what was the point of using shared_ptr? – Dark Matter Jul 21 '20 at 19:15
  • Thank for the reference solution. I came up with some monstrosity 5x times more complex than your reference solution with a static local variable: static private member function, getter method(GetInstance()), shared_ptr and make_shared to make the ptr, and also checking for if shared_ptr is valid in the GetInstance method... – KulaGGin Feb 09 '21 at 17:13
3

My 2nd Question above is that how come private constructor called out side of class while instantiating the static member

//    shared_ptr<Demo> Demo::d(new Demo); // private ctor is accepted 

I think return local static wont work , see below example object destroyed twice

#include <iostream>

using namespace std;

class Demo {
public:
    static Demo & getInstance(){
        static Demo d;
        return d;
    }
    ~Demo(){
        cout << "Demo destroyed" << endl;
    }
};

void fun(){
    Demo l = Demo::getInstance();

}
int main()
{
    fun();
   cout << "Hello World" << endl; 
}
Jagan Arikuti
  • 365
  • 1
  • 2
  • 11
  • You do know that after executing `Demo l = Demo::getInstance();`, you will end up with a copy of your singleton, and thus you will not have a singleton anymore? – Jens Oct 28 '15 at 18:57
  • 3
    Make it `Demo& l = Demo::getInstance();` (note the ampersand). Then mark `Demo`'s copy constructor deleted, as in `Demo(const Demo&) = delete;`, so that your original example doesn't compile anymore. It's not much of a singleton if it can be copied, now is it? – Igor Tandetnik Oct 28 '15 at 19:51
1

Some comments from 1 to help with the discussion. Static variable will be destroyed uppon exit of the application, so we don't need to use the smart pointer at this stage as mentioned above.

"If multiple threads attempt to initialize the same static local variable concurrently, the initialization occurs exactly once (similar behavior can be obtained for arbitrary functions with std::call_once).

Note: usual implementations of this feature use variants of the double-checked locking pattern, which reduces runtime overhead for already-initialized local statics to a single non-atomic boolean comparison. (since C++11)

The destructor for a block-scope static variable is called at program exit, but only if the initialization took place successfully. "

Mickael T
  • 895
  • 2
  • 8
  • 19