32

I have implemented an singleton based on c++ 11. However the constructor can be called multiple times in some cases.

The class will be compiled to static lib and used by other so lib (more than one so lib). And the system is a multi-thread system (run in the Android HAL level)

/// The .h file:

class Logger
{
public:

    /// Return the singleton instance of Logger
    static Logger& GetInstance() {
        static Logger s_loggerSingleton;
        return s_loggerSingleton;
    }

private:

    /// Constructor
    Logger();
    /// Destructor
    ~Logger();
}

/// the .cpp file

Logger::Logger()
{
   ALOGE("OfflineLogger create");
}

Logger::~Logger()
{

}

It should be created once eg:

03-21 01:52:20.785   728  4522 E         : OfflineLogger create

However I can see it has been created more than once

03-21 01:52:20.785   728  4522 E         : OfflineLogger create
03-21 01:52:20.863   728  2274 E         : OfflineLogger create
03-21 01:52:20.977   728  2273 E         : OfflineLogger create
03-21 01:52:26.370   728  4522 E         : OfflineLogger create

Questions:

  1. Anything wrong with my singleton design? Is it a thread-safe issue?

  2. Seems like my singleton works fine in one so scope, but each so lib which includes my singleton will create its own singleton, so that my singleton is no longer “be a singleton”. Is the problem caused from each dynamic linking to new so and the "static variable" becomes "local static"? Is it possible? If so, how to fix?

yivi
  • 23,845
  • 12
  • 64
  • 89
hismart
  • 385
  • 4
  • 14
  • 5
    Don't forget to delete the copy constructor, otherwise it's easy to create multiple objects. It's a good idea to delete assignment, although it might not harm you too much to assign the object to itself – Michael Veksler Apr 02 '19 at 06:11
  • 1
    Also don't forget that there's no synchronization employed here, so it's still prone to race conditions if it's ever used in a multi-threaded context. – Alexander Apr 02 '19 at 10:33
  • 1
    @Alexander That applies to using the object and not to creating it, though. Since C++11, function-scope static variables are guaranteed to be initialised race-free. – Angew is no longer proud of SO Apr 02 '19 at 12:38
  • 1
    @Angew Woah, really? They finally have a purpose! That's great to hear – Alexander Apr 02 '19 at 12:58
  • 4
    Here's an idea: Rather than use a singleton, which is both a hard problem in your environment and [known to be problematic](https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons) for testing and maintenance, just design your code to only create one of the object in question. – T.E.D. Apr 02 '19 at 13:29

5 Answers5

28
  1. Anything wrong with my singleton design? Is it a thread-safe issue?

No. Initialization of function local static variables is guaranteed to be thread-safe by the standard.

  1. Seems like my singleton works fine in one so scope, but each so lib which include my singleton will create its own singleton, so that my singleton is no longer “be a singleton”. Is the problem caused from each dynamic linking to new so and the "staic veriable" become "local static"? Is it possible? If so, how to fix

That is the correct conclusion.

Instead of creating a static library that contains the implementation of the singleton, make it a dynamic library.

R Sahu
  • 196,807
  • 13
  • 136
  • 247
4

Singletons are hard, especially with shared libraries.

Each of your shared libraries has an independent copy of the non-shared library. Without extra care, each will have a copy of the singleton.

In order to have non-trivial singletons, what I have had to do was

  1. Create an extremely low level library to help with singletons -- call it LibSingleton

  2. Create a singleton template that knows the type of the singleton. It uses magic statics to send a request to the LibSingleton with a size, typeid(T).name() key, and type-erased construction and destruction code. LibSingleton returns a reference counting RAII object.

  3. LibSingleton uses a shared mutex to either return a previously constructed object that matches the name/size or constructs it. If it constructs the object, it stores the destruction code.

  4. When the last reference-counted handle to the LibSingleton data goes away, LibSingleton runs the destruction code and cleans up the memory in its unordered map.

This permits really simple singletons to be used nearly anywhere.

template<class T>
class singleton {
public:
  static T& Instance() {
    static auto smart_ptr = LibSingleton::RequestInstance(
      typeid(T).name(),
      sizeof(T),
      [](void* ptr){ return ::new( ptr ) T{}; },
      [](void* ptr){ static_cast<T*>(ptr)->~T(); }
    );
    if (!smart_ptr)
      exit(-1); // or throw something
    return *static_cast<T*>(smart_ptr.get());
  }
protected:
  singleton() = default;
  ~singleton() = default;
private:
  singleton(singleton&&) = delete;
  singleton& operator=(singleton&&) = delete;
};

use looks like:

struct Logger : LibSingleton::singleton<Logger> {
  friend class LibSingleton::singleton<Logger>;
  void do_log( char const* sting ) {}
private:
  Logger() { /* ... */ }
};
Yakk - Adam Nevraumont
  • 235,777
  • 25
  • 285
  • 465
  • 1
    It looks like most of the effort is spent to get the first singleton, and others are relatively easy to add. Still, it makes one wonder ... – David K Apr 02 '19 at 19:56
2

Here's an idea:Rather than use a singleton,which is both a hard problem in your environment and known to be problematic for testing and maintenance,just design your code to only create one of the object in question.

0

static variable should be moved to .cpp file.

Simple way is to keep only declaration of getInstance() in .h and move implementation to .cpp file.

AIMIN PAN
  • 419
  • 3
  • 10
-2

It may be that your header file is being defined multiple times (it's the case if multiple files are including this header file. Try adding a guard around the header file to prevent it from being re-defined if it has already been defined once.

Depending on your C++ compiler, you could actually just add #pragma once as the first line in your file, like this

#pragma once
class Logger
{
    public:

    /// Return the singleton instance of Logger
    static Logger& GetInstance() {
    static Logger s_loggerSingleton;
    return s_loggerSingleton;
}

private:

    /// Constructor
    Logger();
    /// Destructor
    ~Logger();
}

The intended effect is that of the most general alternative, which is to add a macro definition like this

#ifndef LOGGER_H
#define LOGGER_H
class Logger
{
    public:

    /// Return the singleton instance of Logger
    static Logger& GetInstance() {
    static Logger s_loggerSingleton;
    return s_loggerSingleton;
}

private:

    /// Constructor
    Logger();
    /// Destructor
    ~Logger();
}
#endif LOGGER_H
ribbit
  • 897
  • 9
  • 14
  • 7
    Minor niggle: *If you're using C++, you can just add `#pragma once`* should be *If you're using one of many common C++ compilers, you can just add `#pragma once`*. When `#pragma once` has universal, Standard guaranteed support it will become `#once`. – user4581301 Apr 02 '19 at 05:15
  • 1
    Thanks for your hint. I already add the define but forgot to post it in here! I use your second solution. – hismart Apr 02 '19 at 05:20
  • 1
    1. If that was the issue it would not compile ("redeclared class Logger") 2. This will not prevent multiple definitions of the static variable – Benno Straub Apr 02 '19 at 11:35