- Game Development Patterns and Best Practices
- John P. Doran Matt Casanova
- 891字
- 2021-07-02 23:43:46
Templatizing Singletons
Now, assuming we get our Singleton working just the way that we want it to, you may wish to create more Singletons in the future. You could create them all from scratch, but a better thing to do is instead create a consistent approach, creating templates and inheritance to create a single implementation that you can use for any class. At the same time, we can also learn about an alternative way of creating a Singleton class, which will look something like the following:
template <typename T>
class Singleton
{
public:
Singleton()
{
// Set our instance variable when we are created
if (instance == nullptr)
{
instance = static_cast<T*>(this);
}
else
{
// If instance already exists, we have a problem
printf("\nError: Trying to create more than one Singleton");
}
}
// Once destroyed, remove access to instance
virtual ~Singleton()
{
instance = nullptr;
}
// Get a reference to our instance
static T & GetInstance()
{
return *instance;
}
// Creates an instance of our instance
static void CreateInstance()
{
new T();
}
// Deletes the instance, needs to be called or resource leak
static void RemoveInstance()
{
delete instance;
}
private:
// Note, needs to be a declaration
static T * instance;
};
template <typename T> T * Singleton<T>::instance = nullptr;
You'll notice that most of the differences have to do with the class itself. The very first line in our code above uses the template keyword which tells the compiler that we are creating a template, and typename T tells the compiler that, when we create a new object using this, the type T will be replaced with whatever the class we want it to be based on is.
I also want to point out the use of a static cast to convert our Singleton pointer to a T. static_cast is used in code generally when you want to reverse an implicit conversion. It's important to note that static_cast performs no runtime checks for if it's correct or not. This should be used if you know that you refer to an object of a specific type, and thus a check would be unnecessary. In our case, it is safe because we will be casting from a Singleton object to the type that we've derived from it (T).
Of course, it may be useful to see an example of this being used, so let's create an example of a class that we could use as a Singleton, perhaps something to manage the high scores for our game:
class HighScoreManager : public Singleton<HighScoreManager>
{
public:
void CheckHighScore(int score);
private:
int highScore;
};
Notice here that, when we declare our HighScoreManager class, we say that it's derived from the Singleton class and, in turn, we pass the HighScoreManager class to the Singleton template. This pattern is known as the curiously recurring template pattern.
For more information on the curiously recurring template pattern, check out https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern.
After defining the class, let's go ahead and add in an example implementation for the function we've created for this class:
void HighScoreManager::CheckHighScore(int score)
{
std::string toDisplay;
if (highScore < score)
{
highScore = score;
toDisplay = "\nNew High Score: " + std::to_string(score);
printf(toDisplay.c_str());
}
else
{
toDisplay = "\nCurrent High Score: " + std::to_string(highScore);
printf(toDisplay.c_str());
}
}
By using the templatized version of our class, we don't need to create the same materials as in the preceding class. We can just focus on the stuff that is particular to what this class needs to do. In this case, it's checking our current high score, and setting it to whatever we pass in if we happen to beat it.
Of course, it's great to see our code in action, and in this case I used the SplashStage class, which is located in the Mach5 EngineTest project, under SpaceShooter/Stages/SplashStage.cpp. To do so, I added the following bolded lines to the Init function:
void SplashStage::Init(void)
{
//This code will only show in the console if it is active and you
//are in debug mode.
M5DEBUG_PRINT("This is a demo of the different things you can do\n");
M5DEBUG_PRINT("in the Mach 5 Engine. Play with the demo but you must\n");
M5DEBUG_PRINT("also inspect the code and comments.\n\n");
M5DEBUG_PRINT("If you find errors, report to lazersquad@gmail.com");
HighScoreManager::CreateInstance();
HighScoreManager::GetInstance().CheckHighScore(10);
HighScoreManager::GetInstance().CheckHighScore(100);
HighScoreManager::GetInstance().CheckHighScore(50);
//Create ini reader and starting vars
M5IniFile iniFile;
// etc. etc.
In this case, our instance has been created by us creating a new HighScoreManager. If that is not done, then our project could potentially crash when calling GetInstance, so it's very important to call it. Then call our CheckHighScore functions a number of times to verify that the functionality works correctly. Then, in the Shutdown function, add the following bolded line to make sure the Singleton is removed correctly:
void SplashStage::Shutdown(void)
{
HighScoreManager::RemoveInstance();
M5ObjectManager::DestroyAllObjects();
}
With all of that gone, go ahead, save the file, and run the game. The output will be as follows:

As you can see, our code works correctly!
Note that this has the same disadvantages we discussed with our initial version of the script, with the fact that we have to manually create the object and remove it; but it takes away a lot of the busy work when creating a number of Singletons in your project. If you're going to be creating a number of them in your project, this could be a good method to look into.
- Dynamics 365 for Finance and Operations Development Cookbook(Fourth Edition)
- Visual Basic .NET程序設計(第3版)
- 垃圾回收的算法與實現(xiàn)
- Python for Secret Agents:Volume II
- Mastering Selenium WebDriver
- Mastering Ember.js
- JavaScript語言精髓與編程實踐(第3版)
- x86匯編語言:從實模式到保護模式(第2版)
- Python零基礎快樂學習之旅(K12實戰(zhàn)訓練)
- INSTANT CakePHP Starter
- Building RESTful Python Web Services
- ScratchJr趣味編程動手玩:讓孩子用編程講故事
- 運維前線:一線運維專家的運維方法、技巧與實踐
- Python大學實用教程
- BeagleBone Robotic Projects(Second Edition)