0

I have a handrolled and simplistic logsystem - however the way it works it does have to track some global state. The way it is done is through a meyer's singleton that gets initialized on first use. However, this seems to have the drawback: it is possible to call for something to get logged after the singleton has been destroyed (unless the order is known - which can be difficult to assert in larger program) - leading to UB (crash on shutdown most likely).

low-level Log function looks something like this:

void logImpl(const char* log, const std::string& message, Severity::Type level) {
  static LogSys& logSys = LogSys::instance();
  ...
}

I could of course force the problem onto the 'user' of the library, but that doesn't really solve the issue (still manual handling). Will making it an inline static in .h solve anything ?(I guess not). We have the destructor of the singleton run, but is it meaningfull to write to anything to indicate it was destroyed ? another meyer's singleton ? What happends if you initialize a meyer's singleton during static destruction ?

darune
  • 10,480
  • 2
  • 24
  • 62
  • 1
    You can return a `std::shared_ptr` to your singleton if you need to guarantee it outlives other resources. – François Andrieux Feb 01 '19 at 16:34
  • 2
    Why, oh why, oh why is everyone writing their own logger? –  Feb 01 '19 at 16:35
  • 2
    @NeilButterworth because it is easy to make a basic logger, and because nobody has yet written the logger library that is perfect for every imagined use case :) – eerorika Feb 01 '19 at 16:39
  • @eerorika Nobody would (I think) say that the iostream library, or the standard library in general, was "perfect for every imagined use case", but I don't see too many people writing their own versions of these. I really don't get what it is with loggers. –  Feb 01 '19 at 16:43
  • @FrançoisAndrieux That wasn't intended. Also wrong. – YSC Feb 01 '19 at 16:46
  • 1
    Does this help: https://stackoverflow.com/q/40242063/3422652 – Chris Drew Feb 01 '19 at 16:48
  • OP: Could you elaborate on _"unless the order is known - which can be difficult to assert"_? The order of what? – YSC Feb 01 '19 at 16:51
  • Possible this is what you need. The phoenix singelton from Andrei Alexandrescu https://stackoverflow.com/questions/50606016/how-we-place-phoenix-singleton-on-same-address-c – Hatatister Feb 01 '19 at 17:05
  • You can use a `shared_ptr` https://stackoverflow.com/questions/1008019/c-singleton-design-pattern/40337728#40337728 – Galik Feb 01 '19 at 17:21
  • Although I think the meyer singleton should be fine for order of destruction because reverse order of static destruction is guaranteed. But in the unusual case something stored a pointer or reference to the singleton, then it could be a problem. – Galik Feb 01 '19 at 17:24
  • @YSC refering to the order of initialization - between other singletons – darune Feb 01 '19 at 17:32
  • What other singleton? There's only one in your question. – YSC Feb 01 '19 at 17:35
  • @YSC one that is created by the user of library and that is destroyed later for example (the person choose to log something in the destructor) – darune Feb 01 '19 at 17:38
  • @Galik I don't see how that solves anything - the share_ptr itself is just destroyed then. – darune Feb 01 '19 at 17:40
  • @darune The shared pointer will be destroyed but the thing it points to (the logger) will not until everything holding a shared pointer to it dies. So you just need to hold a shared pointer in all your static components that want to log during destruction. – Galik Feb 01 '19 at 17:44

1 Answers1

0

Single responsibility principle: Create a function whose sole purpose is to create the singleton on first use, and return it.

The only case where a static object could be destroyed before logging is if the log message is sent from from the destructor of a(nother) static object. If a destructor (or any other member function) may want to log, then call the singleton getter in the constructor. This enforces the correct order of destruction.

There is still bit of a problem in case the destructor calls a function and logging happens indirectly and unexpectedly. The above convention solves the problem when calling any member function, but the problem remains if there is call to any free function which cannot enforce the order. This problem could be avoided if you constrain yourself from ever using the singleton from any free function. If you cannot bring yourself to such constraint (understandable), then you could simply add call to the constructor of every static object to enforce the order just in case.

None of these constraints can be enforced within the language however, which is unfortunate. This is why dependencies between static objects are to be avoided like the plague - an static objects in general to some lesser degree.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • It's a good point, however I would argue that in a fairly complicated destructor this can be hard to assert what is actually going on or future-proofing it. Eg. destructor calls some other functions, maybe someone decides to log in that function, etc. – darune Feb 01 '19 at 17:16
  • @darune If the destructor is fairly complicated, then perhaps the object should not have static storage. Nevertheless, if you cannot assert that logging won't be involved, then simply call the getter in the constructor to enforce the destruction order. – eerorika Feb 01 '19 at 17:17
  • good point, but could be case - besides it is error prone to have to have to remember initialize in constructor - and I guess what im sort of trying to avoid. – darune Feb 01 '19 at 17:36