2

We are in the process of deprecating ACE libraries in our project which consists of around 120 binaries and in many of binaries we have used ACE_Singleton.Since after deprecating we will not have this class so we are thinking of writing our own generic singleton in our shared library that is common across all these binaries and one of the goals that we want to achieve is if somebody inherit from this class (using CRTP) say Logger and even when Logger constructor is public then also we cannot create two logger object. To illustrate let's assume my singleton class name is GenericSingleton and my client class is logger then following code should throw error :

class Logger:public GenericSingleton<Logger>
{
   public:
      Logger()
      {
      }
};
int main()
{
   Logger obj;// first instance no issue
   Logger obj1; // second instance is problem as it is inherited from singleton 
}

So can somebody suggest me how GenericSingleton should be designed so that while creating second object i should get compiletime error ?

In short is there a way that if my derived class doesn't have private constructor,destructor copy constructor etc. then it can be checked at compiletime using static_assert ?

PapaDiHatti
  • 1,841
  • 19
  • 26
  • You might be able to use e.g. [`std::once_flag`](http://en.cppreference.com/w/cpp/thread/once_flag) to allow the constructor to be called only once. But the most common way is to not have a public constructor at all, to make it private, and have a public `static` "getter" function to get the only instance of the class. – Some programmer dude Sep 10 '16 at 05:45
  • you are right singleton class constructor should be private but when designing library function we should not assume that library users will write proper client classes so i want to handle at library level that if some class inherits from GenericSingleton and don't have private constructor then compile time error should occur – PapaDiHatti Sep 10 '16 at 05:52
  • @Kapil It's nice to want to provide robust and error-proof library code, but to some extent you *must* trust clients to write correct code for themselves. Also, there are many errors that (in C++ at least) simply *cannot* be caught at compile time. – Kyle Strand Sep 10 '16 at 06:32
  • @Kyle I am thinking that complete data type (e.g. Logger) is passed to GenericSingleton template class so can't we inspect that type and identify whether it has private constructor or not – PapaDiHatti Sep 10 '16 at 06:43
  • @Kapil if you're okay with enforcing certain restrictions on the child (singleton) class implementation, then I think you'll like the approach I've added to my answer. – Kyle Strand Sep 10 '16 at 06:53
  • Logger singletons are special in that they may need a lifetime extending beyond the destruction of a corresponding Meyers' singleton (local static variable). And that's really too broad for an answer to an SO question. Andrei Alexandrescu discussed this in detail in his now class Modern C++ Design book; I suggest you look there. – Cheers and hth. - Alf Sep 10 '16 at 06:53
  • @Kapil Inspecting a type at compile time to determine whether a particular member is `private` sounds interesting, but the language doesn't really give you the tools to implement that. – Kyle Strand Sep 10 '16 at 07:02
  • @Kyle Can you explain what is meaning of static T instance { P {} }; in your code – PapaDiHatti Sep 10 '16 at 07:04
  • @Kapil That's the standard "Meyers singleton"--a static function-local instance variable--with C++11 brace-initialization syntax and an in-place construction of the P argument. – Kyle Strand Sep 10 '16 at 07:05

3 Answers3

1

There's no way for a constructor to know at compile time where or how many times it will be called; constructors are just functions, and functions don't know anything about their contexts. Consider that any static_asserts will be evaluated when the class is compiled, but this can (and almost certainly will!) happen in an entirely different translation unit from code that actually instantiates the class.

In any case, it seems unlikely that this would be helpful, because you must have some way to access singletons throughout your codebase.

Additionally, it's unclear why you want to permit your singletons to have public constructors. If you want to enforce singleton behavior at compile time for a completely arbitrary class just by adding an inheritance declaration, you're out of luck; arbitrary classes can be, well, arbitrarily constructed.

Since you're transitioning from the ACE singleton, I suggest you use a similar API; note that the ACE singleton documentation recommends making your singleton constructors private.

If, however, you just want some way to force your client to write a constructor that can't (easily) be called improperly, you can do something like this:

template <typename T>
class SingletonBase {
  protected: class P { friend class SingletonBase<T>; P() = default; };
  public:
     SingletonBase(P) {}
     static T& Instance() {
         static T instance { P {} };
         return instance;
      }
};

(You will also need to delete the base class's copy and move constructors. Here is a working example use case. Note that declaring P's constructor =default does not prevent the singleton class from default-initializing instances of P. )

Now, because the base class constructor takes an argument of type P, the singleton class implementation must pass a P to its parent class constructor. But since the constructor for P is private, the singleton class won't be able to construct an instance of P except by copy or move construction, so its constructor must take an instance of P. But since P itself is protected, only the singleton class and the parent class can actually use it, so effectively the only possible call to the child's constructor must be in the Instance method.

Note that you do not need to explicitly declare and define the singleton-class's constructor, which would be ugly because of the need to use SingletonBase<Singleton>::P. You can simply expose the constructor with a using declaration:

using BASE = SingletonBase<Myclass>;
using BASE::SingletonBase;
Community
  • 1
  • 1
Kyle Strand
  • 15,941
  • 8
  • 72
  • 167
  • @Kapil Well, I'm typing this on a smartphone without an IDE; some typos, etc. are to be expected, and that's honestly not very useful feedback. The error was extremely simple; I've fixed it and added a link to a working example on Coliru. – Kyle Strand Sep 10 '16 at 07:17
  • Apologies for this and thanks for providing working example, i am checking that – PapaDiHatti Sep 10 '16 at 07:20
  • Thanks for suggesting this ingenious approach but in this way i need to pass BASE::P argument to every client class and that doesn't look good – PapaDiHatti Sep 10 '16 at 07:25
  • @Kapil Actually, it appears that this does *not* prevent the singleton class from just instantiating a new P in a default constructor. I'm not sure why, so I'll open a new question about it. – Kyle Strand Sep 10 '16 at 07:25
  • Also I am thinking of my GenericSingleton getInstance and constructor to be variadic template so in that if some client class need arguments in constructor e.g Logger class might need filename, DBClass might need dbname,username,password etc. so that should also be possible – PapaDiHatti Sep 10 '16 at 07:27
  • @Kapil What do you mean "doesn't look good"? As you can see from the example, only the client (singleton) classes need concern themselves with P; and there's no way *not* to place some sort of restriction on them in order to ensure they're singletons. – Kyle Strand Sep 10 '16 at 07:28
  • @Kapil This is the simplest possible implementation to demonstrate the technique. Of course you will need to enhance it for your specific needs. This technique does not prevent variadic forwarding arguments, although that's a little strange in a singleton (what if different calls to `Instance` pass different arguments?). – Kyle Strand Sep 10 '16 at 07:30
  • that's why i want getInstance to be variadic template function so that it can handle any scenario like if somebody making Logger class as singleton can pass string i.e filename as argument and say if somebody wants to make DBUtility singleton can pass string,string,string i.e username,password,dbname as arguments – PapaDiHatti Sep 10 '16 at 07:35
  • Just found out if we put this check static_assert(!std::is_constructible::value, "the constructor is public"); in constructor of generic singleton then we can get compile time error when client class constructor is public – PapaDiHatti Sep 10 '16 at 07:45
  • @Kapil But if you put that check in a `friend` class, it will be meaningless. – Kyle Strand Sep 10 '16 at 07:47
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/123039/discussion-between-kyle-strand-and-kapil). – Kyle Strand Sep 10 '16 at 07:47
  • @Kapil I've fixed the access-violation bug in my code. – Kyle Strand Sep 10 '16 at 15:05
  • I changed std::is_constructible to std::is_destructible, can you check code at http://ideone.com/B5LApr and provide your feedback – PapaDiHatti Sep 11 '16 at 07:37
  • @Kapil The `is_destructible` version does seem to work, though I still don't understand exactly why, so I've [asked a new question about it](http://stackoverflow.com/q/39443705/1858225). – Kyle Strand Sep 12 '16 at 05:20
  • @Kapil However, I have added an addendum; you do not need the "ugliness" of dealing with `P` inside the derived class in order to use my solution. (Also, I believe my solution is *slightly* more robust, since your client could hypothetically declare *multiple* friend classes for a singleton and let one of the classes outside of your control instantiate an instance; that's sort of a silly hypothetical, though, and they'd more or less need to be *trying* to shoot themselves in the foot.) – Kyle Strand Sep 12 '16 at 05:28
  • But with your solution i cannot have private constructor for logger also and i might require that say for opening log file http://ideone.com/nu2itY – PapaDiHatti Sep 12 '16 at 06:19
  • @Kapil Yes, if the constructor needs to actually do any work, then you need to explicitly implement the `Logger(SingletonBase::P)` constructor. However, I would recommend: (1) putting all of the one-time work in an 'initialize' function rather than in the constructor and (2) not keeping the logfile open for the entire duration of the program (this can lead to performance penalties on some platforms, and there's no reason for it). – Kyle Strand Sep 12 '16 at 17:16
0

You need to make sure that instances of Logger cannot be created outside the function that creates the sole instance of Logger.

Here's a simple implementation.

template <typename T> class GenericSingleton {
   public:
      static T& instance() {
         static T instance_;
         return instance_;
      }
};

class Logger: public GenericSingleton<Logger>
{
   // Make sure that the base class can create the sole instance of
   // this class.
   friend GenericSingleton<Logger>;

   private:

      // Makes sure that you cannot create objects of the class
      // outside GenericSingleton<Logger>
      ~Logger() {}

   public:
      void foo() {}
};

int main()
{
   // OK. Call foo() on the only instance of the class.
   Logger::instance().foo();

   // Not OK.
   Logger obj1;
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Here you are dependent on client class to make its destructor private and if by mistake that is not done in client class then code will compile right ? – PapaDiHatti Sep 10 '16 at 06:03
  • @R Sahu So my objective is GenericSingleton is designed in such a way that even if client class have public constructor,destructor,copy constructor etc. then also only one object of class should be allowed and if somebody tries to create other object it should be compilation error – PapaDiHatti Sep 10 '16 at 06:07
  • @Kapil, I don't think you can achieve that. A base class does not have too much control over what a derived class can or cannot do. The only thing a base class can prevent a derived class from doing is override a `virtual` function by declaring it as final. – R Sahu Sep 10 '16 at 06:11
  • @R Sahu, Actually we can throw exception because base class constructor will be called when ever client class object is created so we can check in generic singleton constructor if some count > 1 then throw exception but we want to do that control during compile time – PapaDiHatti Sep 10 '16 at 06:29
  • @Kapil, like I said, I don't think you can pull that off. – R Sahu Sep 10 '16 at 06:38
0

My advice would be to separate concerns. There is the concept of a service (such as a logger) and the service may or may not be a singleton. But this is an implementation detail, and therefore a separate concern. The consumer of the service ought to be agnostic of it.

Now, later in the lifecycle of the project, when you realise that singletons were a terrible idea, you can refactor the singleton without having to refactor any code that depends on it.

e.g.:

template<class Impl>
struct implements_singleton
{
    using impl_type = Impl;
    static impl_type& get_impl()
    {
        static impl_type _{};
        return _;
    }
};

struct logger_impl
{
    void log_line(std::string const& s)
    {
        std::clog << s << std::endl;
    }
};


struct logger
: private implements_singleton<logger_impl>
{
    void log_line(std::string const& s) {
        get_impl().log_line(s);
    }
};

void do_something(logger& l)
{
    l.log_line("c");
}

int main()
{
    logger a;
    logger b;

    a.log_line("a");
    b.log_line("b");   // behind the scenes this is the same logger
                       // but the user need not care

    do_something(a);    
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142