Better Singleton classes C++: Difference between revisions

From OPeNDAP Documentation
⧼opendap2-jumptonavigation⧽
mNo edit summary
mNo edit summary
Line 1: Line 1:


<font color="red">NEVER USE THIS</font>
NB: This was ripped from Google Bard and edited. jhrg 12/27/23


We use lots of Singleton classes in the BES Framework. One issue with that pattern is that memory is usually not returned to the heap before the process exits, leaving tools like valgrind to report the memory as leaked. This is misleading and can be ignored, except that it's a great way to hide ''real'' leaks behind the noise in a sea of false positives. Here's a way around that using C++'s unquie_ptr.
The Meyers Singleton pattern is a popular way to implement the singleton design pattern in C++ using a static member variable declared within a function. It leverages the properties of static functions and objects to guarantee only one instance of the class is ever created.


== The basic plan ==
== Overview ==


<font color="red">NEVER USE THIS</font>
This pattern, developed by Scott Meyers (although G'bard didn't say so...) uses the basic properties of static methods and static variables within functions/methods. The singleton object initialization should happen in the object's default constructor, which is shielded from use by anything other than the singleton class. That's the one thing that makes this pattern somewhat tricky as constructors should not throw exceptions.


I'll use a real example of this from the BES; the BES 'Keys' key-value-pair configuration database. The idea is that we store a static pointer to the single copy of the class to be built. We access that pointer whenever the class is to used with a static method. That method returns a pointer to the class if it has been allocated or allocates a pointer and constructs the object if not. The pattern builds the single instance in a way that is thread safe (using the C++ 11 concurrency features).
== Example ==
 
== What goes in the header ==
 
In the header for the singleton class, include a static member that is a ''unique pointer'' to an instance of the object.


<pre>
<pre>
class TheBESKeys: public BESObj {
class Singleton {
private:
  // Private constructor to prevent direct instantiation
  Singleton() {}


    ...
public:
 
  // Delete the copy constructor and assignment operator to prevent copying
    TheBESKeys() = default;
  Singleton(const Singleton&) = delete;
  Singleton& operator=(const Singleton&) = delete;


    explicit TheBESKeys(const std::string &keys_file_name);
  // Static member function that returns the singleton instance
  static Singleton& getInstance() {
    // Create a local static object the first time the function is called
    static Singleton instance;
    return instance;
  }


    static std::unique_ptr<TheBESKeys> d_instance;
  // Other member functions of the Singleton class...
    static std::once_flag d_euc_init_once;
};


public:
int main() {
  // Access the singleton instance through the getInstance() function
  Singleton& instance1 = Singleton::getInstance();
  Singleton& instance2 = Singleton::getInstance();


    /// Access to the singleton.
  // Verify that both instances are the same object
    static TheBESKeys *TheKeys();
  if (&instance1 == &instance2) {
    std::cout << "Both instances refer to the same Singleton object!" << std::endl;
  }


    ~TheBESKeys() override = default;
  // Use the Singleton instance...


    ...
  return 0;
}
}
</pre>
</pre>


To adapt most existing code to this pattern, move an existing static pointer so that it is a private static '''std::unique_ptr<'''class'''>'''.
== Explanation ==


== What goes in the implementation ==
The Singleton class has a private constructor to prevent direct instantiation.
The getInstance() function is static and declared within the Singleton class.
Inside getInstance(), a static member object instance is declared. This object is only created the first time the function is called.
Subsequent calls to getInstance() simply return the existing instance object.
The copy constructor and assignment operator are deleted to prevent copying the singleton object.


Define the singleton instance a global/file level scope:
=== Benefits of the Meyers Singleton ===


<pre>std::unique_ptr<TheBESKeys> TheBESKeys::d_instance = nullptr;
;Thread-safe: Initialization is guaranteed to happen only once, even in multithreaded environments.
std::once_flag TheBESKeys::d_euc_init_once;</pre>
;Lazy initialization: The singleton object is only created when it is first needed.
;Simple and concise: The implementation is relatively easy to understand and maintain.


The accessor for the static pointer to the instance is likely the only code that needs to be changed in the implementation. Note that the ''std::once_flag'' is a static class member like the unique_ptr<>.
=== Drawbacks of the Meyers Singleton ===


<pre>
;Overuse: Singletons can lead to tight coupling and reduced testability. Use them sparingly and only when truly necessary.
TheBESKeys *TheBESKeys::TheKeys()
;No explicit destruction: The singleton object will be destroyed only when the program exits. This can be problematic if resources need to be explicitly released earlier.
{
    if (d_instance == nullptr) {
        std::call_once(d_euc_init_once, []() {
            d_instance.reset(new TheBESKeys(get_the_config_filename()));
        });
    }


    return d_instance.get();
=== Alternatives to the Meyers Singleton ===
}
</pre>


Note that the constructor for the class is called inside the ''reset()'' method of the ''uniqe_ptr'' object: ''d_instance.reset(new TheBESKeys(get_the_config_filename()));'' and that that is called inside ''std::call_once()''. The use of ''std::call_once()'' ensure that if two threads simultaneously call the accessor, only one instance will be made. In the code above, a lambda was used to pass the 'runnable' to call_once(). If the pointer to the instance (''d_intance'') is not null, then the accessor uses ''unique_ptr::get()'' to return the 'raw' pointer to the instance, which will greatly simplify using this pattern with our existing code.
;Static local variables: This approach can be used within a single file to create a thread-safe singleton.
;Resource acquisition is initialization (RAII): This technique can be used to manage resources associated with the singleton and ensure proper cleanup.
;Dependency injection: This approach can improve testability and decouple the singleton from its dependent classes.

Revision as of 00:14, 28 December 2023

NB: This was ripped from Google Bard and edited. jhrg 12/27/23

The Meyers Singleton pattern is a popular way to implement the singleton design pattern in C++ using a static member variable declared within a function. It leverages the properties of static functions and objects to guarantee only one instance of the class is ever created.

Overview

This pattern, developed by Scott Meyers (although G'bard didn't say so...) uses the basic properties of static methods and static variables within functions/methods. The singleton object initialization should happen in the object's default constructor, which is shielded from use by anything other than the singleton class. That's the one thing that makes this pattern somewhat tricky as constructors should not throw exceptions.

Example

class Singleton {
private:
  // Private constructor to prevent direct instantiation
  Singleton() {}

public:
  // Delete the copy constructor and assignment operator to prevent copying
  Singleton(const Singleton&) = delete;
  Singleton& operator=(const Singleton&) = delete;

  // Static member function that returns the singleton instance
  static Singleton& getInstance() {
    // Create a local static object the first time the function is called
    static Singleton instance;
    return instance;
  }

  // Other member functions of the Singleton class...
};

int main() {
  // Access the singleton instance through the getInstance() function
  Singleton& instance1 = Singleton::getInstance();
  Singleton& instance2 = Singleton::getInstance();

  // Verify that both instances are the same object
  if (&instance1 == &instance2) {
    std::cout << "Both instances refer to the same Singleton object!" << std::endl;
  }

  // Use the Singleton instance...

  return 0;
}

Explanation

The Singleton class has a private constructor to prevent direct instantiation. The getInstance() function is static and declared within the Singleton class. Inside getInstance(), a static member object instance is declared. This object is only created the first time the function is called. Subsequent calls to getInstance() simply return the existing instance object. The copy constructor and assignment operator are deleted to prevent copying the singleton object.

Benefits of the Meyers Singleton

Thread-safe
Initialization is guaranteed to happen only once, even in multithreaded environments.
Lazy initialization
The singleton object is only created when it is first needed.
Simple and concise
The implementation is relatively easy to understand and maintain.

Drawbacks of the Meyers Singleton

Overuse
Singletons can lead to tight coupling and reduced testability. Use them sparingly and only when truly necessary.
No explicit destruction
The singleton object will be destroyed only when the program exits. This can be problematic if resources need to be explicitly released earlier.

Alternatives to the Meyers Singleton

Static local variables
This approach can be used within a single file to create a thread-safe singleton.
Resource acquisition is initialization (RAII)
This technique can be used to manage resources associated with the singleton and ensure proper cleanup.
Dependency injection
This approach can improve testability and decouple the singleton from its dependent classes.