Defaults
Ok, so what happens when I want a setting, but it's nowhere to be found? Most of the time, you'll be happy with the default constructed value, which is 0, false, or yourclass().
In case you aren't, you can also set defaults in ss::init_settings. You can set a default just like you would a normal setting, or you can do bulk defaults; See Listing Eight.
void ss::init_settings() { def_cfg().add_storage("", file_storage("app.ini"); ... // set defaults setting("user.name") = "John"; setting("user.passw") = "secret"; setting("app.retries") = 5; // bulk set of defaults (up to 40 arguments) bulk_setting( "chart.window.width=100", "chart.window.height=50", "tmp.del_period=20"); }
Note that some existing APIs allow you to specify an extra argument, which is to be returned in case the setting is not found. In practice, this just doesn't pay off. You'll forget it some times (in case you need to use the same setting in several places), or even worse, use different defaults in different places. Bugs like that come back to haunt you, so I chose to have a single place where you set the defaults.
Enumerations
In case you have a setting that can be an enumeration, you can persist it as an integer. Or, if you want to be more user friendly, you need to define a to/from string conversion and register it—you'll do this in ss::init_settings. Note that this will work only if boost::is_enum works. Otherwise, you can use enum_ function; check out Listing Nine.
typedef enum answer { yes, no, maybe }; void ss::init_settings() { def_cfg().add_storage("", file_storage("app.ini"); ... register_enum<answer>() (yes,"Yes")(no,"No")(maybe,"Maybe"); } // Using enums in code // ... if boost::is_enum works answer a = setting("user.default_answer"); setting("user.default_answer") = maybe; // ... if boost::is_enum does not work answer a = enum_(setting("user.default_answer")); enum_(setting("user.default_answer") = maybe;
The Need for ss::init_settings
The initialization of the library, the defaults and the enumerations are all done in ss::init_settings. They could be done somewhere else, like, in main(), or with global variables, etc. The reason I chose to do it here is because you might need to get/set the settings before main() as part of some global variable's initialization routine. As "but you shouldn't do it" as this sounds like, in practice this happens quite a bit: log initialization, MFC initialization, etc.
Arrays and Collections
To persist an array or a collection, you basically need to persist all its elements. For an STL-like non-associative array (std::vector, std::list, etc), simply use the array() function. For an associative array (std::map, etc) use the coll() function. In case you have a fixed C-like array (to which you assign using the a[i]=val; syntax), use the array(), and pass the array's size as the second argument. See Listing Ten.
std::vector<int> a; std::map<std::string,long> b; employee c[20]; array(setting("app.widths")) = a; a = array(setting("app.widths")); coll(setting("app.corresp")) = b; b = coll(setting("app.corresp")); array(setting("app.empls"),20) = c; c = array(setting("app.empls"),20);
Note that both the array() and coll() functions are helpers: they simply read or write the array's size, then generate unique names for each element, and read or write them.
Error Handling
Working with settings, you might end up with exceptions. Here are a few:
- The setting does not exist, and does not have a default
- The setting cannot be written to the destination storage (for instance, it's a read-only file)
- The setting is constant, but you tried to write to it
By default, the errors are ignored (in the third case, you'll get a failed assertion). Otherwise, you can set your own error handler (set_error_handler), to which you pass a callback function with two parameters: an error code (integer) and the error string.
Thread Safety
The library is thread safe, by default (using Windows threads). You can override how it deals with thread-safety issues like this:
- SETTING_THREAD_SAFE_USE_BOOST - use boost threads
- SETTING_THREAD_SAFE_USE_WIN - use Windows threads
- SETTING_NOT_THREAD_SAFE - not thread-safe
Unicode Setting Names
By default, the setting names are char-strings. In case you want to override this, just define the SETTING_CHAR macro.
The Code
You can download the code from here or from www.macadamian.ro/drdobbs/. It's tested on VC 7.1 and gcc 4.0.
Acknowledgements
Many thanks to my faithful reviewers: Bartha Bela, Florin Toma, Marius Bucur, Marius Olteanu, Ovi Deac, Ovi Miron, and Ovi Pana.