Gotcha #3: Avoid Global Variables
7 Nov 07, 9:59AM
Gotcha #3: Avoid Global Variables
The use of global variables, i.e. the declaration of a value outside of a specific function, is not recommended. Note global variables should not be confused with global constants, which while on occasion are less than ideal, they are perfectly acceptable for universal constant like PI (=3.141592...) or E (=2.7182...).
One example where you might consider a global variable is for error catching, having defined a class
Error
to store error information you instantiate it once during program start up and all functions that want to report an error have somewhere to dump their error details without complicating their own interfaces. Note this is separate to try {...} catch() { } which you typically use to catch critical program failures rather than failures in user generated data. Therefore to provide local access to your global error stack, you place the following at the top of each file:
extern Error* const errorsAll this seems eminently logical, until someone then asks for two error stacks (or more). However the this global variable
errors
is now used everywhere, so you'll now have to trace through all your code an work out which will need to use the second error stack (lets call this errors2
) and rename all the appropriate errors
to errors2
, oh and also get local reference to your new error stack with:
extern Error* const errors2All this will take you hours and will be prone to a lot of mistakes and heart ache; especially when the client (or manager) changes their mind again.
Better Solution
There are really two options, either- You pass as a new attribute to each function a pointer to the error stack that they should use, i.e.
int fn(int arg1, int arg2, Errors * const errors) { /* ... */ }
- You establish within your Error class a Singleton pattern, using a static to store the first instantiation of each Error class, i.e.
class Error { private: static Error* m_instance; public: static Error &instance() { if(!m_instance) m_instance=new Error; return *m_instance; } protected() Error(); virtual ~Error(); }Now within each function, you merely request a copy of the current instance of the Error class, i.e.
void fn() { Error * const error = Error::instance(); /* ... rest of code ... */ }and use are before. However should you subsequently decide that you would prefer to use an alternative Error class, then you merely change one line, i.e.
void fn() { ErrorNew * const error = ErrorNew::instance(); /* ... rest of code ... */ }So long as ErrorNew and Error have the same underlying interface you be ok. Alternatively you can write a fancies Error class that keeps track of multiple instances, then you can choose which you want, i.e.
void fn() { Error * const error = Error::instance(DATA_ERROR); /* where DATE_ERROR is defined in an enum somewhere */ /* ... rest of code ... */ }