In C++, I frequently run into this problem and always left confused. Suppose there are multiple levels of classes. Each level is instantiating the class which is a level below. E.g. below level1 instantiates level2_a and b(there are more in real case). Now some operation the leaf level object needs to perform. Simple example, say the leaf level object needs to dump some information onto a status console. And all the leaf level objects need to do this. What is the best way to share this "status console" pointer amongst the objects (there could be 100s of these objects)?
Another such example they all need to share a stack onto which they convey some info when they are destroyed? How to share the stack pointer amongst all these leaf level objects
Example:
class level2_a
{
<properties here differ from level2_b>
public:
~level2_a()
{
// dump some info into a common stack here
}
void dump_change_to_value(int newval)
{
// need a console pointer here
// but can't be singleton because there's 1 per window
}
};
class level2_b
{
<properties here differ from level2_a>
public:
~level2_b()
{
// dump some info into a common stack here
}
void dump_change_to_value(int newval)
{
// need a console pointer here
// but can't be singleton because there's 1 per window
}
};
class level1
{
private:
level2_a* ml2_a;
level2_b* ml2_b;
public:
<func members..>
};
How to accomplish sharing of the stack/console amongst level2_a and b
Usually, I set up the logger in the base class, through a co-class that is often a singleton.
I obviously always define some basic loggers:
stdout
/stderr
or not).It's obvious that, in fact, it's the same class but instanciated with different file handles. The internal singleton instances are stored in a map, where the key part is the tuple of values passed to the getInstance
method. Then, once the instance is known, each class using it stores it in a shared_ptr
.
Default is to set up your logger in the base class, through static attributes that will be used as "default logger" when a new derivated instance is created. But each instance can also set its own, "private" logger, for debug purpose... For example, you can set the "NullLogger" to base class, and set the "FileLogger" to your currently debugged derivated class.
And, obviously, the rest of your program can still use a "SysLogger" to continue the normal logs in a centralized place, like rsyslog
or anything like this.
All this rely only on static members that hold the default values, normal attributes (set in constructor and/or in logger's setter) that are the ones really used, and obviously virtual methods.
Note that "default" means "logger connected at instanciation", so changing the default logger isn't propagated to all child instances... Unless you constantly use a ternary operator when accessing logger (with a line similar to (mThisLogger?mThisLogger:sDefLogger)->log(...)
that can be put in an inline private, inherited function).
The nice part is that you don't have to care about the logger after its initial connection, and it's even propagated automatically if needed (simply needs mutexes/signals to change it without messing up everything). But you can still change the logger for a particular instance, so that you can set a different verbosity level just for this instance, or send the log to a more convenient destination (like a clean console, standalone file, ...) without changing anything else in the code, with something like this:
#ifdef _DEBUG // or a "if (isDebug())" if you have such a function...
annoyingInstance->setLog(allLoggersNamespace::DebugLogger);
annoyingInstance->setLogLevel(Logger::FullTrace);
#endif