DGehlhaar
DGehlhaar

Reputation: 161

make_unique doesn't compile for creating a singleton instance

All,

I am using C++14 and am making a more-or-less standard Singleton. I am using the latest Visual Studio 2017. This code works:

#include <memory>
class A
{
public:
  static A& getInstance()
  {
    if (instance == nullptr)
      instance = std::unique_ptr<A>(new A());
    return *instance;
  }

private:
  A() {}
  static std::unique_ptr<A> instance;
};
std::unique_ptr<A> A::instance = nullptr;

However, when I change the creation of the singleton instance to this:

instance = std::make_unique<A>();

I get a compilation error that I am trying to access a private member:

Error   C2248   'A::A': cannot access private member declared in class 'A'      
c:\program files (x86)\microsoft visual studio\2017\professional\vc\tools\msvc\14.14.26428\include\memory   2510    

This feels like a bug to me, as the two forms should be identical in function? Thoughts?

Upvotes: 4

Views: 5689

Answers (3)

AshkanVZ
AshkanVZ

Reputation: 778

To sum up the others answers' and fix some flaws in them: You can make a private structure with a private constructor which is a friend with your class. Then make your class constructor public but with an additional argument of that private structure.

Also its better to use static function witch return a reference instead of the bare static variable.

#include <memory>

class A
{
  struct Private
  { 
    friend A;
    private:
      explicit Private() = default; 
  };

public:
  static A * getInstance()
  {
    if (!instance())
      instance() = std::make_unique<A>(Private());

    return instance();
  }

  A(Private) {};

private:
  static std::unique_ptr<A> & instance()
  {
    static std::unique_ptr<A> inst;
    return inst;
  }
};

Or if you really dont need any special configurations which requires using a pointer and heap allocation (like initializing the instance in a special thread or ...) :

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

private:
  A() = default;
};

Upvotes: 0

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275395

instance = std::make_unique<A>();

this creates the A within the function make_unique. But the ctor you want to call is private.

private:
  struct ctor_permission_t{
    explicit ctor_permission_t(int){};
  };
public:
  explicit A(ctor_permission_t):A(){}
};

then

instance = std::make_unique<A>(ctor_permission_t{0});

The ctor_permission_t acts as a token giving its possessor the right to construct an A. We pass this to make_unique.

The explicit int ctor in ctor_permission_t makes it impossible to create it without naming the type, and only within instances and friends of A can name it because it is private. This makes it difficult to bypass this permission token.

Upvotes: 4

Adrian W
Adrian W

Reputation: 5026

The purpose of std::unique_ptr<> is to control the lifetime of the object pointed to. You may pass a std::unique_ptr<> around, but that will also transfer ownership of the object pointed to. This concept does not match very well with the concept of a singleton. There is only one place that is ever allowed to create (or delete) the singleton. You don't really need a std::unique_ptr<>for that. As already said in the comments, there are simpler ways for that. I would prefer @Praetorian's suggestion:

static A& getInstance() 
{
     static A instance;
     return instance;
}

The reason why you can't use std::make_unique<>() to instantiate your class is that the constructor is private. To access it, you need the accessing function to be a friend. Have a look at How to make std::make_unique a friend of my class for that. Another solution is to provide a public constructor requiring a private type as argument, as described int this solution.

Upvotes: 6

Related Questions