Skip to content

Commit 0374780

Browse files
authored
Merge pull request #11 from M1TE5H/main
RealWorld Singleton and Visitor Examples
2 parents ba08fc4 + 522d06b commit 0374780

File tree

4 files changed

+474
-0
lines changed

4 files changed

+474
-0
lines changed

src/Singleton/RealWorld/Output.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//// Logger Singleton ////
2+
**** LOGGER START UP ****
3+
1 [WARNING]
4+
Be careful with this potential issue.
5+
2 [INFO]
6+
Here are some extra details.
7+
3 [ERROR]
8+
A major problem has caused a fatal stoppage.
9+
**** LOGGER SHUT DOWN ****

src/Singleton/RealWorld/main.cc

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/**
2+
* EN: Real World Example of the Singleton Design Pattern
3+
*
4+
* Need: Consider a (large) program that must implement its own internal logging
5+
* functionality with a global logger object. Suppose that all log messages are
6+
* required to be printed in order even if the logger is called across multiple
7+
* concurrent threads or processes. Furthermore, the logger should have some
8+
* sort of flag to specify and ignore messages below a certain level.
9+
*
10+
* Solution: A thread-safe Logger class can be implemented using the Scott
11+
* Meyers' Singleton pattern. The Singleton pattern is the recommended solution
12+
* if indeed there must be a single global instance of the Logger class.
13+
* However, in modern practices, the addition of a new singleton to a codebase
14+
* could be regarded as a design flaw with the singleton itself being a design
15+
* anti-pattern. Nevertheless, the following presents a Logger Singleton as a
16+
* commonly appearing use case of the pattern in the C++ literature.
17+
*/
18+
19+
#include <iostream>
20+
#include <mutex>
21+
#include <string>
22+
#include <thread>
23+
24+
/**
25+
* EN: The Logger Singleton Class
26+
*
27+
* In this (zero handle objects) implementation of the Scott Meyers' Singleton,
28+
* the constructor and destructor are private methods and the move/copy
29+
* constructors and assignment operations are explicitly deleted. In essence,
30+
* the program itself cannot directly create an instance of the Logger class,
31+
* and instead the static instance() member function must be used to access it.
32+
*
33+
* The public API of this Logger has two main callbacks: (1) set the level of
34+
* the Logger; and (2) log a message at given level. For convenience, these two
35+
* client-facing methods wrap around the instance() member function in a
36+
* thread-safe fashion. An integral counter member is also included to
37+
* demonstrate that the message ordering is preserved.
38+
*
39+
* Note the final keyword specifier prevents inheritance, that is, it is not
40+
* possible to extend this Logger Singleton and override its class methods.
41+
*/
42+
43+
class Logger final {
44+
public:
45+
/**
46+
* EN: Various levels for the log messages can be labelled here; the choice of
47+
* the level member establishes a threshold below which log messages are
48+
* ignored.
49+
*/
50+
enum class Level : unsigned {
51+
debug = 0,
52+
info = 1,
53+
warning = 2,
54+
error = 3,
55+
/* ... */
56+
};
57+
58+
public:
59+
/**
60+
* EN: The Public API of this Logger
61+
*
62+
* Note that both of these methods must be implemented in a thread-safe
63+
* manner, hence the mutex as a static member.
64+
*/
65+
static void level(Level);
66+
static void log(std::string const &, Level level = Level::debug);
67+
68+
public:
69+
/**
70+
* EN: Prevention of Copy and Move Construction
71+
*/
72+
Logger(Logger const &) = delete;
73+
Logger(Logger &&) = delete;
74+
75+
/**
76+
* EN: Prevention of Copy and Move Assigment Operations
77+
*/
78+
Logger &operator=(Logger const &) = delete;
79+
Logger &operator=(Logger &&) = delete;
80+
81+
/**
82+
* EN: Public Instantiator Method
83+
*
84+
* In a typical Singleton, this static member function would enable access to
85+
* the Singleton. In this implementation of a Logger class, it is called
86+
* inside of the bodies of the public API methods.
87+
*/
88+
static Logger &instance();
89+
90+
private:
91+
/**
92+
* EN: Private Constructor and Destructor
93+
*/
94+
Logger();
95+
~Logger();
96+
97+
private:
98+
static std::mutex mutex_;
99+
static std::ostream &os_;
100+
static std::size_t count_;
101+
static Level level_;
102+
};
103+
104+
/**
105+
* EN: Static members of the Logger class need to be defined outside of the
106+
* class itself.
107+
*/
108+
std::mutex Logger::mutex_;
109+
std::ostream &Logger::os_{std::cout};
110+
std::size_t Logger::count_{0};
111+
Logger::Level Logger::level_{Logger::Level::debug};
112+
113+
/**
114+
* EN: Magic Static (c.f. Scott Meyers' Singleton)
115+
*
116+
* The instance() method creates a local static instance of the Logger class,
117+
* which is guaranteed thread-safe initialisation without manual thread
118+
* synchronisation. Note that this does not guarantee the thread safety of other
119+
* members; the RAII (Resource Acquistion Is Initialisation) principle should be
120+
* used to lock and unlock the mutex.
121+
*
122+
* Note that there will be a performance penalty each time this method is
123+
* called as there will be a check to see if the instance has already been
124+
* initialised.
125+
*/
126+
Logger &Logger::instance() {
127+
static Logger instance;
128+
return instance;
129+
}
130+
131+
/**
132+
* EN: Logger Level Modifier Method
133+
*
134+
* This thread-safe setter allows the client to alter the (global) level member
135+
* of the Logger.
136+
*/
137+
138+
void Logger::level(Logger::Level level) {
139+
std::lock_guard<std::mutex> lock(mutex_);
140+
instance().level_ = level;
141+
}
142+
143+
/**
144+
* EN: Enummeration-to-String Helper Function
145+
*
146+
* This implementation is naive but nonetheless useful for distinguishing the
147+
* different kinds of log messages.
148+
*/
149+
std::string to_string(Logger::Level level) {
150+
switch (level) {
151+
case Logger::Level::debug:
152+
return "[DEBUG]";
153+
case Logger::Level::info:
154+
return "[INFO]";
155+
case Logger::Level::warning:
156+
return "[WARNING]";
157+
case Logger::Level::error:
158+
return "[ERROR]";
159+
/* ... */
160+
default:
161+
return "[LEVEL]";
162+
}
163+
};
164+
165+
/**
166+
* EN: Thread-Safe Log Method
167+
*
168+
* If the message level is at or above the threshold level of the Logger
169+
* Singleton, then the counter is incremented and the message is printed.
170+
* Otherwise, the message is ignored and the counter remains as is.
171+
*
172+
* Note again the usage of RAII for mutex locking/unlocking should this method
173+
* be called in a thread.
174+
*/
175+
void Logger::log(std::string const &message, Logger::Level level) {
176+
std::lock_guard<std::mutex> lock(mutex_);
177+
if (static_cast<int>(level) < static_cast<int>(instance().level_))
178+
return;
179+
instance().os_ << ++instance().count_ << '\t' << to_string(level) << "\n\t"
180+
<< message << '\n';
181+
}
182+
183+
/**
184+
* EN: Constructor and Destructor
185+
*
186+
* The print statements indicate when these methods are called in the program.
187+
*/
188+
Logger::Logger() { std::cout << "****\tLOGGER\tSTART UP\t****" << '\n'; }
189+
Logger::~Logger() { std::cout << "****\tLOGGER\tSHUT DOWN\t****" << std::endl; }
190+
191+
/**
192+
* EN: Client Code: Logger Singleton Usage
193+
*
194+
* The desired Log Level is set which also instantiates the Logger class; the
195+
* log() methods can then be invoked e.g. via lambdas within different threads.
196+
*/
197+
int main() {
198+
199+
std::cout << "//// Logger Singleton ////\n";
200+
201+
Logger::level(Logger::Level::info);
202+
203+
std::thread t1(
204+
[] { Logger::log("This is just a simple development check."); });
205+
std::thread t2(
206+
[] { Logger::log("Here are some extra details.", Logger::Level::info); });
207+
std::thread t3([] {
208+
Logger::log("Be careful with this potential issue.",
209+
Logger::Level::warning);
210+
});
211+
std::thread t4([] {
212+
Logger::log("A major problem has caused a fatal stoppage.",
213+
Logger::Level::error);
214+
});
215+
216+
t1.join();
217+
t2.join();
218+
t3.join();
219+
t4.join();
220+
221+
return EXIT_SUCCESS;
222+
}

src/Visitor/RealWorld/Output.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"menu":[{"item":"food","name":"Borscht","calories":"160kcal","label":"meat"},{"item":"food","name":"Samosa","calories":"250kcal","label":"vegetarian"},{"item":"food","name":"Sushi","calories":"300kcal","label":"fish"},{"item":"food","name":"Quinoa","calories":"350kcal","label":"vegan"},{"item":"drink","name":"Vodka","volume":"25ml","label":"alcholic"},{"item":"drink","name":"Chai","volume":"120ml","label":"hot"},{"item":"drink","name":"Sake","volume":"180ml","label":"alcholic"},{"item":"drink","name":"Kola","volume":"355ml","label":"cold"}]}

0 commit comments

Comments
 (0)