1

I have a C++ Log class (implementation is long and probably not important) where I've overloaded the << operator so I can use statements like log << "Error" << endl; to make it more intuitive (and to allow me to bifurcate the output to screen and log, if need be). The class works fine; I instantiate it in Main() and can use it without issue. I'd like to be able to use it in other, non-related classes without actually passing it into them (via class constructor or similar) because if I pass by copy, I end up running certain housekeeping log class methods twice and if I pass by reference, I have to dereference the pointer in the external classes to use it (*log << "Error" << endl;). Is this possible? What is the best way to do it?

Kenn G
  • 49
  • 5
  • 3
    "If I pass by reference, I have to dereference the pointer in the external classes" No, you don't. You're talking about pointers, not references. – juanchopanza Sep 14 '15 at 14:15

4 Answers4

2

Create a global function with the following declaration:

Log& log();

Its implementation can look like this:

Log& log()
{
    static Log log;
    return log;
}

Or like this, avoiding order-of-destruction issues (which might happen when the destructor of another object with static storage duration writes something to the log):

Log& log()
{
    static Log* log = new Log; // never deleted
    return *log;
}

You can then use your logger with almost the same syntax as before:

#include "log.h"

// ...

void f()
{
    log() << "Error\n";
}

This has two advantages over the Singleton approach:

  • It's simpler.
  • It keeps the actual concern of the log class (writing log messages) separated from a particular allocation strategy.

By the way...

because if I pass by copy, I end up running certain housekeeping log class methods twice

Not sure what you mean, but if you pass by copy, then the class needs to be copyable, and it usually does not make sense to copy a logger which encapsulates or accesses external resources. That's why standard stream classes (std::ostream et al) are not copyable.

and if I pass by reference, I have to dereference the pointer in the external classes to use it

No. You mix up pointers and references. They are quite different language features. Dereferencing is for pointers, not for references.

Christian Hackl
  • 25,191
  • 3
  • 23
  • 53
  • Some believe [omitting a call to a non-trivial destructor being UB](http://stackoverflow.com/questions/1978709/are-memory-leaks-undefined-behavior-class-problem-in-c). – alain Sep 14 '15 at 15:19
  • @alain: Although this question is quite an interesting read, the general consensus there is that it's not UB, that the wording of the standard is poor and that even if it might theoretically be UB, the actual behaviour in any known implementation can be relied upon, i.e. it's a language-lawyer issue. In any case, it's not relevant here. Obviously, you would not use this technique if you required the `Log` destructor to run! :) – Christian Hackl Sep 14 '15 at 15:33
  • Yes, I totally agree that it doesn't matter in practice. Actually I like this solution, thanks for sharing. – alain Sep 14 '15 at 15:48
0

I know all the reasons object oriented purists object to the following idea, but it has worked well for me for logging and similar activities in a variety of large projects. It avoids a number of practical problems in other designs:

Make a base class with pure virtual methods for the thing your logging class can do. Make a static member of the base class, which will be a pointer to the singleton instantiation of that class, with that pointer filled in by the constructor, and available by a static accessor method. So you typically use it as:

 base_logger::singleton() << whatever...

The actual logger class is constructed once (wherever convenient before first use) which implicitly calls that base_logger constructor locking in the pointer to itself, which will be returned as a reference by that singleton() function.

Likely you'll want different names for base_logger and singleton. I'm just describing how it fits together for convenient use, not suggesting those names.

JSF
  • 5,156
  • 1
  • 9
  • 19
  • Logger classes are typically a valid use case for a singleton. Anyway the interface to connect the underlying logger instance could be a locally instantiated or member class. – πάντα ῥεῖ Sep 14 '15 at 14:28
0

One thing that you can do is make the logger class using the static singleton design pattern.

This means that there is only ever one instance of it created (usually at static storage duration in the logger.cpp compilation unit), and there is a globally visible, static method of class logger that returns a reference to this. Then, you don't need to pass the object around into constructors or functions or anywhere else -- wherever you want to use it, you can simply grab the singleton.

This pattern is indeed usually considered harmful if overused. However, for a logger utility it seems very appropriate -- a logger can quite reasonably be considered an application-level resources which can be appropriately tied to the lifetime of the program. The singleton pattern can make it painful to create proper unit tests, but usually you aren't terribly concerned with unit testing the logger framework either -- if your log messages aren't working correctly usually you'd spot that right away.

Chris Beck
  • 14,006
  • 3
  • 43
  • 79
  • I know what a 'singleton' is; how does that differ from a 'static singleton'? I thought perhaps a singleton declared as static (`static class Singleton`) but that wouldn't work because I do need to open a file with a variable name...but maybe it's something different? – Kenn G Sep 14 '15 at 15:12
  • @KennG: I guess I'm not sure if there is a formal or widely acknowledged difference between 'singleton' and 'static singleton'. There are a lot of ways to skin the cat though, and you can make a 'singleton' class without using keyword 'static'. I've seen code where, some class would only have 1 instance at a time, and a part of it needed to be globally accessible. But maker did not want it to be static singleton, i.e. get a reference from anywhere. – Chris Beck Sep 14 '15 at 15:21
  • So, in `.cpp` file, in ctor impl, he writes `assert(instance == NULL); instance = this;`, and in the dtor he writes `assert(instance == this); instance = NULL;` and makes some globally visible functions that call certain methods on the (file scope) `instance` pointer. So it improves encapsulation a bit, only some of the methods are made globally available. I'm not going to say this is elegant... actually this pattern of coding is a bit smelly imo. Point is, I think its a case of a class that is a singleton without being a static singleton. (Arguably.) – Chris Beck Sep 14 '15 at 15:22
0

I would make it as simple as possible, and instanciate a single global Log object (not a singleton actually):

Log log;

and declare it extern in a common header file:

extern Log log;

With this, you can use your existing syntax everywhere.

alain
  • 11,488
  • 2
  • 28
  • 47
  • 2
    This is a bit dangerous. Consider the following: I have a class `Foo` and a static instance of it somewhere. And the destructor logs something: `Foo::~Foo() { log << "destroying foo\n"; }` The program may try to access `log` which was already destroyed. This is a general problem with global objects, of course. – Christian Hackl Sep 14 '15 at 15:05
  • Yes, that's true. It affects singletons as well, I think the only way to avoid it is your approach with `new` that never gets deleted. Otoh, logging the destruction of static objects is not needed usually. – alain Sep 14 '15 at 15:13
  • Not usually. But if someone somewhere at some point does it, you will have a very, very hard time finding the error, especially if the logging does not happen directly in the destructor but in some function called by the destructor. – Christian Hackl Sep 14 '15 at 15:16