Reputation: 3167
Working on updating some old code and the original authors decided that all the command line argument variables should be globals. This obviously makes things more challenging, from a testing and development standpoint.
My question is how do I best manage command line arguments that all classes need to use (for example a trace flag / debugging flag). A coworker suggested at the very least wrapping the variables in a namespace, but that just doesn't seem sufficient. I thought about a singleton or static class and just providing getters but that doesn't seem very elegant. On the other hand that seems better than having to pass 5 configuration options to every class that needs to know if debugging and a handful of other options are set though.
Upvotes: 3
Views: 309
Reputation: 5244
The biggest problem with global variables is that changing them from within a function tends to become an unexpected side effect which introduces bugs. In the case of command line arguments, however, they are essentially constants as far as the running process is concerned. The only thing preventing you from declaring them const
is that they need to be assigned when you are parsing the command line in the beginning.
I would suggest creating some mechanism that allows you to initialize the arguments in the beginning, but then prevents any part of the program from ever changing them. That will effectively avoid any disadvantage that global variables would normally introduce.
One way might be a ProgramArguments
class/struct with const members that are initialized in the constructor, by parsing the command line. You could then have something like:
std::unique_ptr<ProgramArguments const> g_program_arguments;
int main(int argc, char* argv[])
{
g_program_arguments.reset(new ProgramArguments(argc, argv));
if(g_program_arguments->verbose)
std::cout << "verbose!" << std::endl;
// ...
return 0;
}
That wouldn't prevent you from changing the pointer to point to a different ProgramArguments instance however. Another way might be to temporarily cast away the constness for initialization purposes:
struct ProgramArguments {
ProgramArguments() {}
bool verbose;
};
ProgramArguments const g_program_arguments;
void init_program_arguments(int argc, char* argv[])
{
ProgramArguments& program_arguments = const_cast<ProgramArguments&>(g_program_arguments);
program_arguments.verbose = true;
}
int main(int argc, char* argv[])
{
init_program_arguments(argc, argv);
if(g_program_arguments.verbose)
std::cout << "verbose!" << std::endl;
return 0;
}
Upvotes: 4
Reputation: 4411
This will depend on the amount of globals we are talking about. Personally, I think it is fine to have a few globals for things like debug flags and say a singleton Log Manager.
If you really want to respect OOP principles by the book, then you would have to pass everything a function or object needs as parameters. Never accessing global state. As you have mentioned, passing a lot of common parameters to every function gets boring pretty quickly, so one pattern that might help you alleviate this is the Context Object.
Upvotes: 0