2

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?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
DGehlhaar
  • 111
  • 2
  • 8
  • 7
    Kind of a duplicate of https://stackoverflow.com/q/33905030/241631. `make_unique` is a function just like any other, why should it be able to call private members of your class? – Praetorian Jun 22 '18 at 21:21
  • 6
    In any case, you can avoid all this by defining the function as `static A& getInstance() { static A instance; return instance; }` – Praetorian Jun 22 '18 at 21:22
  • @Praetorian Except that leaves a possibility of a Static DE-initialization Order Fiasco. – aschepler Jun 22 '18 at 21:24
  • @aschepler How? I don't see any additional limitations compared to the OP's version. – Praetorian Jun 22 '18 at 21:27
  • 2
    Both solutions suffer from race conditions and from the problem of reentrancy. That said, singletons are usually considered a bad idea, so I'd avoid that anyway. – Ulrich Eckhardt Jun 22 '18 at 21:28
  • @Praetorian in this particular case the static initialization would be fine; but in my specific case it is a large and complex object; and the static initialization order might be problematic; I have learned that dynamic allocations in this sort of instance can resolve a whole lot of headaches. Although make_unique() is a function I would have expected the two forms of initialization to be synonymous. – DGehlhaar Jun 22 '18 at 21:33
  • 4
    @UlrichEckhardt What is the race condition in Praetorian's solution? The Meyers Singleton is thread-safe as of C++11, and the OP has indicated they are using C++14. – Nevin Jun 22 '18 at 21:33
  • Ulrich in this case the code will be single-threaded; protecting this for thread-safety is straightforward. I disagree with singletons being bad ideas as a general principle; but they need to be used judiciously. – DGehlhaar Jun 22 '18 at 21:38
  • 2
    @DGehlhaar As Nevin said, what I posted is already thread safe post C++11. And I still don't see a static initialization order problem that dynamic allocation will solve compared to a function local static (which is only initialized the first time you call `getInstance`) – Praetorian Jun 22 '18 at 21:41
  • @Praetorian in general if you have a type that depends on another static type (let's say that "A" makes reference to "B" which contains a static variable) you can get into all kinds of trouble regarding which is initialized in what order; it can get messy; I have spent a lot of time dealing with this sort of issue. And it is compiler-dependent. I try to use dynamic allocation for this sort of thing if possible. – DGehlhaar Jun 22 '18 at 21:47
  • 1
    Using the approach with a pointer from multiple threads seems to be obviously a race condition, just because two threads could simultaneously write the shared pointer object. I'd take Nevin's word that the non-pointer approach works as of C++ 11. Further, if creation of the singleton instance indirectly involved access to that instance (circular dependency) that would probably end bad. Usually infinite recursion, but that depends on the code in between. For the non-pointer variant, it could involve use of a partially constructed object instead. – Ulrich Eckhardt Jun 22 '18 at 21:47
  • @DGehlhaar: "need to be used judiciously" -- that's true. I'd prefer a compile-time error. For that I use dependency injection, which also makes code easier to test in isolation. – Ulrich Eckhardt Jun 22 '18 at 21:52
  • @DGehlhaar But it seems to me that dynamic allocation won't protect you from static initialization order if your dynamic object relies on static objects that have not been initialized. – Galik Jun 22 '18 at 21:58
  • I recommend this: https://stackoverflow.com/a/1008289/3807729 and put **all** your static objects inside functions that return their references. – Galik Jun 22 '18 at 21:59

3 Answers3

6

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.

Adrian W
  • 4,563
  • 11
  • 38
  • 52
  • Where is it specified that you should make `make_unique` a friend? – curiousguy Jun 23 '18 at 00:05
  • The constructor for `class A` is private. `std::make_unique<>()` needs to access that constructor. That's why OP gets a compile error. So, _if_ OP still wants to use `std::make_unique<>()` for the singleton (which I and many commenters above would not recommend), then making `std::make_unique<>` a friend of the class to instantiate would solve the problem. – Adrian W Jun 23 '18 at 09:00
  • "_std::make_unique<>() needs to access_" Yes **I do see it**. Is that **documented**? Where is **guaranteed** to be the case? You know, some sort of "contract". – curiousguy Jun 23 '18 at 21:45
  • @curiousguy ISO/IEC 14482 2014 ch. 20.8.1.4 [unique.ptr.create]: _Returns_: `unique_ptr(new T(std::forward(args)...))`. Obviously, to create an object of type `T`, you need to have access to the constructor. You may also want to have a look at https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique – Adrian W Jun 24 '18 at 06:38
  • Does it say anything about friend? – curiousguy Jun 24 '18 at 06:47
  • I don't think there is any guarantee that `make_unique` doesn't use a helper function, so making it a `friend` isn't sufficient. – Nevin Jun 25 '18 at 19:09
  • @Nevin This has already been worked out [here](https://stackoverflow.com/a/33905304). – Adrian W Jun 25 '18 at 20:15
4
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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

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;
};
AshkanVZ
  • 681
  • 6
  • 14