0 Background

What is singleton?

Here is the wiki reference: https://en.wikipedia.org/wiki/Singleton_pattern

In software engineering, the singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. The term comes from the mathematical concept of a singleton.

Don’t know what it is talking about hah? Of course, I do not either.

Let start with case/code.

I was/still be a Starcraft player, and one big fan of ‘Ghost’ of Terran. Why? Ghost controls fantastic attack – nuclear weapon.

-‘Nuclear launch detected’

Yes, this is a alarm which means big trouble is coming because there is a Ghost aiming your army with nuclear weapon. But, of course, this is not a article to introduce anything about Starcraft. I just want to imagine to be the dope one, that I am just the super hero, using the device to aim the target with nuclear attack.

So there must be one program running on the device, and, of course, one device controls only one nuclear weapon due to the hardware limitation and requirement. Let’s start with the code,

Attention, my environment is,

Linux version 3.17.7-200.fc20.x86_64 (gcc version 4.8.3 20140911 (Red Hat 4.8.3-7) (GCC) )

At first, we need the one class Nuclear

//Nuclear.h

#ifndef _NUCLEAR_H_
#define _NUCLEAR_H_
#include

class NNuclear
{
public:
enum NStatus
{
STATUS_READY,
STATUS_UNKNOWN,
};
NNuclear();
~NNuclear();
void Initialize();
bool IsReady();
private:
uint8_t* Payload;
NStatus Status;
};
#endif
//Nuclear.cpp
#include "Nuclear.h"
#include
//Mb
#define NUCLEAR_PAYLOAD_SIZE 100
NNuclear::NNuclear():
Status(STATUS_UNKNOWN),
Payload(NULL)
{

}

NNuclear::~NNuclear()
{
if (Payload)
{
delete Payload;
}
}

void NNuclear::Initialize()
{
Payload = new uint8_t[NUCLEAR_PAYLOAD_SIZE * 1024 * 1024];
memset(Payload, 0xF5, NUCLEAR_PAYLOAD_SIZE * 1024 * 1024);
Status = STATUS_READY;
return;
}

bool NNuclear::IsReady()
{
return (Status == STATUS_READY);
}

And then, We need a nuclear launcher to make the nuclear ready.

//NNuclearLauncher.h
#ifndef _NUCLEAR_LAUNCHER_H_
#define _NUCLEAR_LAUNCHER_H_
#include "Nuclear.h"
class NNuclearLauncher
{
public:
NNuclearLauncher();
~NNuclearLauncher();
void Launch();

private:
NNuclear* Nuclear;
};
#endif
//NuclearLaucher.cpp
#include "NuclearLauncher.h"
#include "stdio.h"
NNuclearLauncher::NNuclearLauncher():
Nuclear(NULL)
{
Nuclear = new NNuclear();
}

void NNuclearLauncher::Launch()
{
printf("[Launcher]\t Launching Nuclear....\n");
Nuclear->Initialize();

//To do
//Launching Scenario

printf("[Launcher]\t Launching Nuclear Completed\n");
return;
}

NNuclearLauncher::~NNuclearLauncher()
{
if (Nuclear)
{
delete Nuclear;
}
}

Also, a monitor is needed to be informed, when nuclear is ready and, give the ‘Nuclear Launch Detcted…’

//NuclearMonitor.h
#ifndef _NUCLEAR_MONITOR_H_
#define _NUCLEAR_MONITOR_H_
#include "Nuclear.h"
class NNuclearMonitor
{
public:
NNuclearMonitor();
~NNuclearMonitor();
void OnNuclearStatusChanged();
private:
NNuclear* Nuclear;
};
#endif
//NuclearMonitor.cpp
#include "NuclearMonitor.h"
#include
NNuclearMonitor::NNuclearMonitor():
Nuclear(NULL)
{
Nuclear = new NNuclear();
}

void NNuclearMonitor::OnNuclearStatusChanged()
{
if(Nuclear->IsReady())
{
printf("[Monitor]\t Nuclear Launch Detected...\n");
}
else
{
printf("[Monitor]\t Nuclear is Not Ready!\n");
}
return;

}

NNuclearMonitor::~NNuclearMonitor()
{
if(Nuclear)
{
delete Nuclear;
}
}

And a simple Makefile is like this,

### Requires

### Targets
SRC += *.cpp
TARGET += Nuclear

##Build
edit: $(SRC)
g++ $(SRC) -o $(TARGET)

After Build and execute, we get the output,

[Monitor]     Nuclear is Not Ready!
[Launcher]    Launching Nuclear....
[Launcher]    Launching Nuclear Completed
[Monitor]     Nuclear is Not Ready!

Oops, it does not meet our requirement.

1 Why Singleton?

This is a very obvious mistake I have made in the code above. Yes, the line highlighted is where the issue is caused. We have two Nuclear instances totally independent in NuclearMonitor and NuclearLauncher, which means the one in monitor is not been launched while the one in launcher as completed the launch scenario.

How to debug? Quite simple, just pass the Nuclear instance in launcher to monitor, everything will be fine, just like this without hesitation.

//NNuclearLauncher.h
#ifndef _NUCLEAR_LAUNCHER_H_
#define _NUCLEAR_LAUNCHER_H_
#include "Nuclear.h"
class NNuclearLauncher
{
public:
NNuclearLauncher();
~NNuclearLauncher();
void Launch();
NNuclear* GetNuclear() { return Nuclear; };
private:
NNuclear* Nuclear;
};
#endif
//NuclearMonitor.h
#ifndef _NUCLEAR_MONITOR_H_
#define _NUCLEAR_MONITOR_H_
#include "Nuclear.h"
class NNuclearMonitor
{
public:
NNuclearMonitor();
~NNuclearMonitor();
void OnNuclearStatusChanged();
void SetNuclear(NNuclear* nuclear);
private:
NNuclear* Nuclear;
};

#endif
//NuclearMonitor.cpp
#include "NuclearMonitor.h"
#include
NNuclearMonitor::NNuclearMonitor():
Nuclear(NULL)
{
//    Nuclear = new NNuclear();
}

void NNuclearMonitor::OnNuclearStatusChanged()
{
if(Nuclear->IsReady())
{
printf("[Monitor]\t Nuclear Launch Detected...\n");
}
else
{
printf("[Monitor]\t Nuclear is Not Ready!\n");
}
return;

}

void NNuclearMonitor::SetNuclear(NNuclear* nuclear)
{
Nuclear = nuclear;
}

NNuclearMonitor::~NNuclearMonitor()
{
if(Nuclear)
{
//    delete Nuclear;
}
}
//main.cpp
#include "NuclearLauncher.h"
#include "NuclearMonitor.h"
#include

int main()
{
NNuclearLauncher launcher;
NNuclearMonitor monitor;
monitor.SetNuclear(launcher.GetNuclear());
monitor.OnNuclearStatusChanged();

launcher.Launch();

monitor.OnNuclearStatusChanged();
return 0;
}

Yes, after updated by the code highlighted, get the nuclear instance out from the launcher and set it into monitor. Of course, the instance is allocated in the launcher and should be managed/release in the launcher, so the release part of nuclear instance in monitor has been removed.
After make&execute, we get what we expected, ‘Nuclear launch detected’,

[Monitor]     Nuclear is Not Ready!
[Launcher]    Launching Nuclear....
[Launcher]    Launching Nuclear Completed
[Monitor]     Nuclear Launch Detected...

So here comes the question, why singleton?
Sure, we can get/set instance through open new interface for classes to implement these kind of requirement, but design pattern never solves the problems of implementation.

From the design point of view, we need to reduce interconnection of every component, and ensure the architect extendable. Supposing we need NuclearPayloadCalculator to calculate the payload size, and then the launcher will need another interface to obtain payload size for nuclear initializing(No related code provided, but trail is not hard to implement). Also, to each object that uses Nuclear instance, how to alloca/dealloc/manage this instance has so much dependency from each other, which is expected to be solved by the architect design. In this kind of implementation, we have to get the object that own the nuclear instance even it is not designed to exist in the scope because we need it to pass the nuclear instance. In this case, which is the most important, I think, is nuclear instance could be allocated in any classes. If the memory requirement of payload is huge for the device, I think this is very dangerous to cause the OOM which should never happen because only one nuclear instance is expected to exist in the system.

Let’s summary the requirement,

  1. Only one Nuclear instance exists in global scope at one time. Another Nuclear instance allocation should be forbidden if current one has not been released.
  2. Instance management should be implemented within the Nuclear class itself.
  3. Getter/setter interface could be implemented only in Nuclear class itself.
  4. Extend-ability need to be ensured, which means dependency and update need to be minimum in the component using Nuclear instance when requirement updated/submitted.
2. Singleton Implemetation

Requirement has been listed above, so let’s start from the very beginning one by one.

1, One static private member of type Nuclear is needed in Nuclear class,

2, No getter/setter exists for this static private member, Nuclear class itsself manage this static instance.

3. static interface to initialize/destroy this static instance should be declared and implemented in Nuclear class,

4. Better to forbidden any operation to new/delete Nuclear instance, so it is better to make the constructor/destructor private.

5. Better to design a static reference count to help destroy/re-initialize the static Nuclear instance.

Here we will have the Nuclear class declaration

//Nuclear.h

#ifndef _NUCLEAR_H_
#define _NUCLEAR_H_
#include

class NNuclear
{
public:
static NNuclear* GetInstance();
void DestroyInstance();
enum NStatus
{
STATUS_READY,
STATUS_UNKNOWN,
};
// NNuclear();
// ~NNuclear();
void Initialize();
bool IsReady();
private:
NNuclear();
~NNuclear();
static NNuclear* Singleton;
static uint32_t ReferenceCount;
uint8_t* Payload;
NStatus Status;
};

#endif

And here, we have the implementation

//Nuclear.cpp
#include "Nuclear.h"
#include
//Mb
#define NUCLEAR_PAYLOAD_SIZE 100

uint32_t NNuclear::ReferenceCount = 0;
NNuclear* NNuclear::Singleton = NULL;

NNuclear* NNuclear::GetInstance()
{
if(Singleton == NULL)
{
Singleton = new NNuclear();
}
ReferenceCount++;
return Singleton;
}

void NNuclear::DestroyInstance()
{
if (ReferenceCount > 0)
{
ReferenceCount--;
}
if (ReferenceCount == 0 && Singleton != NULL)
{
delete Singleton;
Singleton = NULL;
}
return;
}

NNuclear::NNuclear():
Status(STATUS_UNKNOWN),
Payload(NULL)
{

}

NNuclear::~NNuclear()
{
if (Payload)
{
delete Payload;
}
}

void NNuclear::Initialize()
{
Payload = new uint8_t[NUCLEAR_PAYLOAD_SIZE * 1024 * 1024];
memset(Payload, 0xF5, NUCLEAR_PAYLOAD_SIZE * 1024 * 1024);
Status = STATUS_READY;
return;
}

bool NNuclear::IsReady()
{
return (Status == STATUS_READY);
}

Of course, we don’t need any getter/setter in Launcher/Observer, also Nuclear instance alloc/de-alloc related stuff should be update,

//NuclearLaucher.cpp
#include "NuclearLauncher.h"
#include "stdio.h"
NNuclearLauncher::NNuclearLauncher():
Nuclear(NULL)
{
//    Nuclear = new NNuclear();
Nuclear = NNuclear::GetInstance();
}

void NNuclearLauncher::Launch()
{
printf("[Launcher]\t Launching Nuclear....\n");
Nuclear->Initialize();

//To do
//Launching Scenario

printf("[Launcher]\t Launching Nuclear Completed\n");
return;
}

NNuclearLauncher::~NNuclearLauncher()
{
if (Nuclear)
{
//        delete Nuclear;
Nuclear->DestroyInstance();
}
}
//NuclearMonitor.cpp
#include "NuclearMonitor.h"
#include
NNuclearMonitor::NNuclearMonitor():
Nuclear(NULL)
{
//    Nuclear = new NNuclear();
Nuclear = NNuclear::GetInstance();
}

void NNuclearMonitor::OnNuclearStatusChanged()
{
if(Nuclear->IsReady())
{
printf("[Monitor]\t Nuclear Launch Detected...\n");
}
else
{
printf("[Monitor]\t Nuclear is Not Ready!\n");
}
return;

}

NNuclearMonitor::~NNuclearMonitor()
{
if(Nuclear)
{
//    delete Nuclear;
Nuclear->DestroyInstance();
}
}
//main.cpp
#include "NuclearLauncher.h"
#include "NuclearMonitor.h"
#include

int main()
{
NNuclearLauncher launcher;
NNuclearMonitor monitor;
monitor.OnNuclearStatusChanged();

launcher.Launch();

monitor.OnNuclearStatusChanged();
return 0;
}

Let’s make it and run, check the result,

[Monitor]     Nuclear is Not Ready!
[Launcher]     Launching Nuclear....
[Launcher]     Launching Nuclear Completed
[Monitor]     Nuclear Launch Detected...

Of course, everything works fine~ We have not update any implementation.

3. Summary
  1. Singleton pattern is one of the most simple design pattern, so it is one kind of design pattern. It would not help solve any issue of implementation
  2. As talked before, code would be written only once but read millions, it helps make the code and logic more clear.
  3. Singleton helps the extension, and trouble shooting. Unit test  could be applied to ensure the singleton class is fine, and then any debug/new feature could be gotten rid of it unti the architecture is re-designed.
  4. This case is the simplest singleton. Singleton pattern application could be extended more far, such as thread-safe ones.
Advertisements