0

I have a lot of global settings defining the behavior of my program. I found it convenient to declare these settings as extern constants in a separate namespace like this:

// settings.hpp

namespace settings
{
    extern const int waitEventTimeoutSeconds;
    // ...
}
// settings.cpp

#include "settings.hpp"

const int settings::waitEventTimeoutSeconds = 3;
// ...
// some.cpp

#include "settings.hpp"

bool waitForEvent(args...)
{
    const int timeout = settings::waitEventTimeoutSeconds;
    // Do stuff
}

It works fine, but in order to change some settings the user needs to edit settings.cpp file and recompile the stuff, which is not very convenient. I want some parseConfig() function that would be able to read a user provided config file once at startup and set global constants according to the content of that file:

void parseConfig(userConfigPath)
{
    settings::eventWaitTimeoutSettings = ...; // But it is const and global
}

Are there any tricks to achieve this?

Of course, I can just use some Config class for storing the settings, but this is why I don't like this approach:

Config userConfig(path);

bool bar(args..., timeout);

bool foo(args..., timeout)
{
    // Do stuff
    return bar(args..., timeout);
}

bool bar(args..., timeout)
{
    // Do stuff
    return waitForEvent(args..., timeout)
}

bool waitForEvent(args..., timeout)
{
    int waitFor = timeout;
    // Do stuff
}

int main()
{
    foo(args..., userConfig.waitEventTimeoutSeconds);
}

If using config object, a reference to this object or its members should be passed everywhere, which makes code more clunky. In the above example, when you look at foo() call in main() it is unclear why does this function need this weird userConfig.waitEventTimeoutSeconds parameter? Neither foo() nor bar() uses this parameter directly, so passing it just for further passing to a deeply nested function does not look like an elegant solution. This is why I prefer the global constants approach.

But can I somehow initialize all my global constants in a separate function?

Alexey104
  • 969
  • 1
  • 5
  • 17
  • 2
    *"If using config object, a reference to this object or its members should be passed everywhere"* - you can have a global `config` object the same way you can have a global variable of any other type. I therefore don't really understand the problem you see with this approach – UnholySheep Aug 03 '22 at 09:45
  • 1
    You should also consider that you probably want to unit test your code. In such scenarios maybe you want to test specific classes/functions with different kinds of settings. This can be achieved through Dependency Injection. I am not aware of how big your code base is, but passing around so-called `Traits` where a member could be a `Settings` object is a general approach to tackle this problem – RoQuOTriX Aug 03 '22 at 09:46
  • 1
    "reference to this object or its members should be passed everywhere, which makes code more clunky" thats a frequent argument for global variables. Though, what appears "clunky" to you now, is more readable and maintainable on the long run. global variables are almost never the better option – 463035818_is_not_an_ai Aug 03 '22 at 09:50
  • 1
    `global variables are almost never the better option` - but they are global constants, not variables. – Alexey104 Aug 03 '22 at 09:53
  • 3
    They are no longer constants when you read them from file. It literally changes - program starts, they have some value, then you open file and assign to them - they are not const anymore. – KamilCuk Aug 03 '22 at 09:54
  • I would first separate the "const"-ness property from the "global"-ness property - do you *really* need both ? – OrenIshShalom Aug 03 '22 at 09:54
  • @OrenIshShalom - `do you really need both ?`. I want the settings to be accessible everywhere in the program, but I also want to prevent any function from accidentally changing them, so I want them to be const. – Alexey104 Aug 03 '22 at 10:06
  • 3
    Global variables are the road to hell. Global 'setting' variables are a greased road to hell. It may seem a chore to pass settings down the call stack but all experience in code bases of any size is it helps. The enemy is state complexity and global variables are arbitrary and difficult to find state complexity. It's almost as bad to pass a generic 'Settings' object all around the place. It's fine to have a Settings Object but you should only pass the settings relevant to some leg/module of the program into that leg/module. – Persixty Aug 03 '22 at 10:15

1 Answers1

1

Do not use global variables. They make stuff confusing, like the problems you are having. Make it all local. Write getters and if needed setters. Force the user to call a function to get the configuration, where you do the stuff you want.

// settings.hpp
struct TheSettings {
   int waitEventTimeoutSeconds;
};

const TheSettings& settings();

// settings.cpp
const TheSettings& settings() {
    static TheSettings cachesettings;
    static bool cacheready = false;
    if (!cacheready) {      
        // open file
        // construct settings
        cachesettings.waitEventTimeoutSeconds = read_from_file;
        cacheready = true;
    }
    return cachesettings;
}


// main.cpp
int main() {
   // instead of `::` type `().`
   std::cout << settings().waitEventTimeoutSeconds;
}

You might be interested in reseraching C++ Singleton design pattern and Static Initialization Order Fiasco .

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • You are contradicting yourself in your first sentence. A Singleton (the design pattern from GoF) is nothing more than global variables in disguise (if implemented like this). Singletons can be useful but not if they are globally accessible – RoQuOTriX Aug 03 '22 at 10:00
  • 2
    I agree. I meant, that global variables with nice clothes are better than naked global variables. Sometimes (in case of settings, for example) "global" is what you want. – KamilCuk Aug 03 '22 at 10:02
  • That last comment killed me ! very funny :) – OrenIshShalom Aug 03 '22 at 10:25