Quicksillver
Quicksillver

Reputation: 307

Singleton Implementation in C++

I'm trying to implement a singleton in C++. I'm fairly new to the language so please explain if I made any trivial error.

Below is my Header File:

#pragma once
#include <glad\glad.h>
#include <GLFW\glfw3.h>
#include "Imgui\imgui.h"
#include "imgui_impl_glfw_gl3.h"


class GUI
{

    static GUI * instance;

private:

    GUI();

public:

    static GUI * Instance();

    void Init(GLFWwindow * window);
    void Loop();
    void Draw();
    void End();

    ~GUI();
};

And part of the Class file.

GUI::GUI()
{
    instance = nullptr;
    // static GUI * instance = nullptr;
}


GUI * GUI::Instance()
{
    if (nullptr == instance) {
        instance = new GUI();
    }

    return instance;
}

I feel like it should work, however, I'm getting this error when I try to execute.

Error Image.

I am fairly new to this, so any help as to why I am erroring out would be appreciated.

Upvotes: 0

Views: 3164

Answers (3)

Francis Cugler
Francis Cugler

Reputation: 7895

When working with OOP design and talking about Singleton types: the primary objective is that a Singleton is a type of Object that you will only ever need 1 instance of the object during the life of the program's or application's run time. What I typically do is I create a Singleton Class that is a base class that all singleton types will inherit from. I won't show all the derived classes, but I will show the Singleton class:

Singleton.h

#ifndef SINGLETON_H
#define SINGLETON_H

class Singleton {
public:
    // Number Of Items In Enum Type Must Match The Number
    // Of Items And Order Of Items Stored In s_aSingletons
    enum SingletonType {
        TYPE_LOGGER = 0, // MUST BE FIRST!
        TYPE_SETTINGS,
        TYPE_ENGINE,
        TYPE_ANIMATION_MANAGER,
        TYPE_SHADER_MANAGER,
        TYPE_ASSET_STORAGE,
        TYPE_AUDIO_MANAGER,
        TYPE_FONT_MANAGER,
        TYPE_BATCH_MANAGER,     
    }; // Type

private:
    SingletonType m_eType;

public:
    virtual ~Singleton();

protected:
    explicit Singleton( SingletonType eType );

    void logMemoryAllocation( bool isAllocated ) const;

private:
    Singleton( const Singleton& c ); // Not Implemnted
    Singleton& operator=( const Singleton& c ); // Not Implemented
}; // Singleton

#endif // SINGLETON_H

Singleton.cpp

#include "Singleton.h"

#include "Logger.h"
#include "Settings.h"

struct SingletonInfo {
    const std::string strSingletonName;
    bool              isConstructed;

    SingletonInfo( const std::string& strSingletonNameIn ) :
        strSingletonName( strSingletonNameIn ),
        isConstructed( false )
    {}
}; // SingletonInfo

// Order Must Match Types Defined In Singleton::SingeltonType enum
static std::array<SingletonInfo, 9> s_aSingletons = { SingletonInfo( "Logger" ),
                                                      SingletonInfo( "Settings" ),
                                                      SingletonInfo( "Engine" ),
                                                      SingletonInfo( "AnimationManager" ),
                                                      SingletonInfo( "ShaderManager" ),
                                                      SingletonInfo( "AssetStorage" ),                                                  
                                                      SingletonInfo( "AudioManager" ), 
                                                      SingletonInfo( "FontManager" ),
                                                      SingletonInfo( "BatchManager" ) };

// ----------------------------------------------------------------------------
// Singleton()
Singleton::Singleton( SingletonType eType ) :
m_eType( eType ) {
    bool bSaveInLog = s_aSingletons.at( TYPE_LOGGER ).isConstructed;

    try {
        if ( !s_aSingletons.at( eType ).isConstructed ) {
            // Test Initialization Order
            for ( int i = 0; i < eType; ++i ) {
                if ( !s_aSingletons.at( i ).isConstructed ) {
                    throw ExceptionHandler( s_aSingletons.at( i ).strSingletonName + " must be constructued before constructing " + s_aSingletons.at( eType ).strSingletonName, bSaveInLog );
                }
            }
            s_aSingletons.at( eType ).isConstructed = true;

            if ( s_aSingletons.at( TYPE_ENGINE ).isConstructed && 
                 Settings::get()->isDebugLoggingEnabled( Settings::DEBUG_MEMORY ) ) {
                logMemoryAllocation( true );
            }

        } else {
            throw ExceptionHandler( s_aSingletons.at( eType ).strSingletonName + " can only be constructed once.", bSaveInLog );
        }
    } catch ( std::exception& ) {
        // eType Is Out Of Range
        std::ostringstream strStream;
        strStream << __FUNCTION__ << " Invalid Singelton Type sepcified: " << eType;
        throw ExceptionHandler( strStream, bSaveInLog );
    }
} // Singleton

// ----------------------------------------------------------------------------
// ~Singleton()
Singleton::~Singleton() {
    if ( s_aSingletons.at( TYPE_ENGINE ).isConstructed &&
         Settings::get()->isDebugLoggingEnabled( Settings::DEBUG_MEMORY ) ) {
        logMemoryAllocation( false );
    }
    s_aSingletons.at( m_eType ).isConstructed = false;
} // ~Singleton

// ----------------------------------------------------------------------------
// logMemoryAllocation()
void Singleton::logMemoryAllocation( bool isAllocated ) const {
    if ( isAllocated ) {
        Logger::log( "Created " + s_aSingletons.at( m_eType ).strSingletonName );
    } else {
        Logger::log( "Destroyed " + s_aSingletons.at( m_eType ).strSingletonName );
    }
} // logMemoryAllocation

Now the class above will not compile directly because it relies on other class objects that is coming from a large library, but this is shown for demonstration purposes of how one might design or implement a Singleton.

As you can see from the class's declaration above there are different types of objects in this library that are all Singletons that can be seen in both the class's enumerated type as well as the static array of SingletonInfo objects which is a struct containing information about the Singleton found in the *.cpp file.

All of these types:

  • Logger
  • Settings
  • Engine
  • AnimationManager
  • ShaderManager
  • AssetStorage
  • AudioManager
  • FontManager
  • BatchManager

Are all Singletons and there is only ever 1 instance of these class types that are available in my library simply because they all inherit from the above class. The base class is also responsible for the order of construction of these object since some object might need to exist first before another one is created and this class also makes sure that once an object is constructed that you can no longer construct another instance of that class; it will throw an exception. This class is also thread safe because several of the derived classes are using multithreading processes.

Now as for your static get() method for getting the class's static pointer; I do not implement this in the base class. Each Derived class that is a singleton implements it's own method for return back a static pointer that is set to it's this pointer upon successful creation.


Now as for a derived class that inherits from this it would look something like this for its static pointer method.

Derived.h

#include "Singleton.h"

#ifndef DERIVED_H
#define DERIVED_H

class Derived final : public Singleton {
public:
    static Derived* const get();

    /*Data Type*/ doSomething( /* ... */ ) { /* ... */ }
};

#endif // DERIVED_H

Derived.cpp

#include "Derived.h"

static Settings* s_pSettings = nullptr;

Settings::Settings() :
Singleton( TYPE_DERIVED ) { // Must also be in the enumerated type and the static array
    s_pSettings = this;
}

Now to use this derived type:

main.cpp

#include "Derived.h"
#include "SomeOtherClass.h"

int main() {
    Derived derived; // Create your singleton Instance.

    SomeOtherClass soc( /*some data to initialize or construct*/ );

    soc.someFunc( /* ... */ );

    return 0;
}

Let's say that SomeOtherClass used Derived and needs access to its static pointer this is how one would do it:

SomeOtherClass.h

#ifndef SOMEOTHERCLASS_H
#define SOMEOTHERCLASS_H

class SomeOtherClass {
public:
    SomeOtherClass( /* ... */ );

     void someFunc( /* ... */ );
    // Class Details Here                   
};

#endif // SOMEOTHER_CLASS_H

SomeOtherClass.cpp

#include "SomeOtherClass.h"
#include "Derived.h"

static Derived* s_pDerived = nullptr;

SomeOtherClass::SomeOtherClass( /* ... */ ) {
    s_pDerived = Derived::get(); // Only If Derived Was Already Created First
}

void SomeOtherClass::someFunc( /* ... */ ) {
    s_pDerived->doSomething( /* ... */ );     
}

Now as for pointers of these derived singleton types that are allocated dynamically I prefer to use either a std::shared_ptr<> or a std:unique_ptr<> depending on my needs. This helps to avoid memory leaks and also provides a nice way of handling access and ownership of my dynamic objects.

Upvotes: 1

flmng0
flmng0

Reputation: 427

A much easier way to implement a singleton is to do

GUI * GUI::Instance()
{

    static GUI* instance = new GUI();
    retune instance;

}

Doing it this way, you don't have to set any fields within the GUI class.

Note: if you want to use references instead of pointers, replace asterisks(*) in the code above with ampersands(&).

Upvotes: 2

squill25
squill25

Reputation: 1208

You need to declare static instance variables both in header and implementation files, ie

GUI * GUI::instance;

needs to be somewhere in the implementation (cpp) file.

Upvotes: 0

Related Questions