Reputation: 307
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.
I am fairly new to this, so any help as to why I am erroring out would be appreciated.
Upvotes: 0
Views: 3164
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
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
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