Runtime Constants
When settings are this easy, you can get carried away. In fact, a lot of the application constants can become runtime constants (that is, be read as settings). This brings flexibility up a gear—which is great! Imagine doing profiling—you go through the .ini file, change a few values, see how the application behaves. Tuning your app will become a lot less painful.
One thing to remember is that when using a runtime constant, use the const_ function, instead of setting. You'll have a few advantages:
- Readability: the one reading the code will understand it's a read-only setting (more important: a constant)
- when using const_, the library will enforce that you can't use it as a setting at all. In debug, you'll get a failed assertion. In release, you can choose to throw an exception (see "Error Handling" below)
Note that you can use forced get when using const_. Listing Four shows you how to use the constants.
int r = const_("user.retries"); std::string welcome = const_("app.welcome_msg"); int max_users = const_("app.max_users"); int def_size = const_<int>("w.def_width") * const_<int>("w.def_height");
The Dot Makes a Point
The setting names contain dots in them. This is intentional. First of all, it delimits scope of the setting: "chart.window.width" tells you that it's a setting related to the "chart" module, it's about its window, and it's about the window's width.
More than that, the dots can specify the actual storage destination: be it a file, a read-only file, the registry, some web site, etc. Based on the name of the setting (as you'll see below), the library will select where it reads or writes the setting.
The predefined storage classes are file_storage and registry_storage, but you can extend the library, by adding your own storage classes.
Note that for a setting, its type must be IOstream friendly (have operator<< defined, in order to be read, and operator>> defined, in order to be written). As a bonus, I allow for automatic conversions between strings of different types (from std::string to std::wstring, and vice versa).
The storage class will always deal with the setting values as strings. However, for each setting it deals with, it will also know its original type, thus, it can optimize the storage. For instance, the registry storage class will keep integers as DWORDs in the registry. See Listing 5.
struct setting_storage { ... virtual void get_setting( const std::string &name, std::string &val, const std::type_info&) const = 0; virtual void set_setting( const std::string &name, const std::string &val, const std::type_info& ) = 0; ... }; struct registry_setting_storage : setting_storage { ... virtual void set_setting( const std::string &name, const std::string &val, const std::type_info& t) { if ( t == typeid(int) || t == typeid(long) || t == typeid(short) || ...) write_to_registry_as_DWORD(name, val); else write_to_registry_as_SZ_string(name,val); } };
As a side note, the file_storage class has an option to save the settings at a certain interval (on a dedicated thread). file_storage also allows you to have comments, as shown in Listing 6.
# when connecting to server, # and it fails, how many times should we try again? app.retry_count=10 # when delete dummy temporary files, at what period should we do it? app.del_temp_files_ms=5000