Here is the code of my extension to the library boost, which plays a role similar to the QSettings class in Qt, except that it is implemented more and more in the spirit of boost.

It supports arbitrary values ​​for fields, their serialization and strict visitor-based type support.

I would like to hear comments on the code, what could have been done better, etc., in short, give me a code review . If anyone is interested, I can post the unit test code.


 #include <boost/any.hpp> #include <boost/variant.hpp> #include <boost/algorithm/string.hpp> #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree_serialization.hpp> #include <boost/property_tree/xml_parser.hpp> #include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/ini_parser.hpp> #include <boost/property_tree/info_parser.hpp> #include "core_defines.h" #include "core_extensions_lexical_cast.h" namespace boost { namespace property_tree { // Generic value holder, which will attempt to cast objects rather than // indicate error if wrong type was supplied. This behavior is likely to // be more error-prone, but is more 'user-friendly'. See 'strict_value_holder' // class if you need strict type support. // Supports trivial integral, floating point and string types in 'strict' // mode and also allows the storage to hold arbitrary values using // 'boost::any' storage. Note that due to implementation details, // values of specific (non-default) types can't be serialized // (we skip them / replace with dummy objects). class value_holder { // Main value holder type, which has support for common types // and can use the internal visitor for casting. typedef boost::variant<bool, int, unsigned, float, double, std::string, std::wstring> Holder; public: // Models 'DefaultConstructible', default constructor // leaves the storage uninitialized. value_holder() : using_any_holder_(false) { } template <typename T> value_holder(const T& value, typename boost::enable_if< typename boost::mpl::contains<Holder::types, T> >::type* = 0) : holder_(value), using_any_holder_(false) { } // Custom constructing routine for string-like types. explicit value_holder(const char* value) : holder_(std::string(value)), using_any_holder_(false) { } // Custom constructing routine for string-like types. explicit value_holder(const wchar_t* value) : holder_(std::wstring(value)), using_any_holder_(false) { } // Custom constructing routine for non-standard types. template <typename T> value_holder(const T& value, typename boost::disable_if< typename boost::mpl::contains<Holder::types, T> >::type* = 0) : any_holder_(value), using_any_holder_(true) { } // Retrieves held value with possible additional type casts. // Note that this method won't even compile for unsupported // types. template <typename T> typename boost::enable_if<typename boost::mpl::contains<Holder::types, T>, T>::type as() const { // Apply internal casting visitor. return boost::apply_visitor(type_casting_visitor<T>(), holder_); } // Attempts to retrieve non-standard type from internal 'boost::any'-based // storage. Throws 'boost::bad_any_cast' on errors. template <typename T> typename boost::disable_if<typename boost::mpl::contains<Holder::types, T>, T>::type as() const { // Apply internal 'boost::any_cast' routine. return boost::any_cast<T>(any_holder_); } // Generic holder swapping (required by 'boost::property_tree' // specification). void swap(value_holder& value) { holder_.swap(value.holder_); any_holder_.swap(value.any_holder_); std::swap(using_any_holder_, value.using_any_holder_); } // Check if current holder is empty (required by // 'boost::property_tree' specification). bool empty() const { // Dispatch emptiness check based on currently // used value holder. if (using_any_holder_) return any_holder_.empty(); return holder_.empty(); } private: // Internal functional object used to retrieve common types and perform // appropriate casting. template <typename T> struct type_casting_visitor : boost::static_visitor<T> { // Handles every possible arithmetic type with // 'boost::numeric_cast'. template <typename Y> T operator()(const Y& value) const { return boost::numeric_cast<T>(value); } // Template specialization for 'std::string' -> type casting template <> inline T operator()<std::string>(const std::string& value) const { return boost::lexical_cast<T>(value); } // Template specialization for 'std::wstring' -> type casting template <> inline T operator()<std::wstring>(const std::wstring& value) const { return boost::lexical_cast<T>(value); } }; // Template specialization for type -> 'std::string' casting. template <> struct type_casting_visitor<std::string> : boost::static_visitor<std::string> { template <typename Y> std::string operator()(const Y& value) const { return boost::lexical_cast<std::string>(value); } }; // Template specialization for type -> 'std::wstring' casting. template <> struct type_casting_visitor<std::wstring> : boost::static_visitor<std::wstring> { template <typename Y> std::wstring operator()(const Y& value) const { return boost::lexical_cast<std::wstring>(value); } }; public: // Custom serialization implementation. template <typename Archive> void serialize(Archive& ar, const unsigned int) { using namespace ::boost; ar & serialization::make_nvp("using_any_holder", using_any_holder_); // Serialize only what we can - if the value shares one // of the predefined types, use the default 'boost::variant' // serialization routine. if (!using_any_holder_) ar & serialization::make_nvp("holder", holder_); } private: // Main value holder instance. Holder holder_; // Alternative value holder, which is used to store // objects and data that differ from supported by main holder. // These can actually be treated as temporary objects, because // due to language / implementation limitations, they aren't // serialized / deserialized (it's impossible to combine benefits // from 'generics' while still having the strict typing - which // is a requirement for our serialization routines). boost::any any_holder_; // Indicates if the current value is actually using a 'boost::any'- // based value holder (non-common value). bool using_any_holder_; }; // Strict variation of generic value holder, which // does not support arbitrary types and will // throw if types do not match exactly. class strict_value_holder : public value_holder { // Main value holder type, which has support for common types. typedef boost::variant<bool, int, unsigned, float, double, std::string, std::wstring> Holder; public: // Models 'DefaultConstructible'. strict_value_holder() { } // Custom constructors for any of the available basic cases (which are // supported by initial 'boost::variant' placeholder). template <typename T> strict_value_holder(const T& value, typename boost::enable_if< typename boost::mpl::contains<Holder::types, T> >::type* = 0) : holder_(value) { } // Custom constructing routine for string-like types. explicit strict_value_holder(const char* value) : holder_(std::string(value)) { } // Custom constructing routine for string-like types. explicit strict_value_holder(const wchar_t* value) : holder_(std::wstring(value)) { } // Retrieves held value without any type casts. Throws 'boost::bad_get' if // casting was unsuccessful. template <typename T> typename boost::enable_if<typename boost::mpl::contains<Holder::types, T>, T>::type as() const { return boost::get<T>(holder_); } // Generic holder swapping (required by 'boost::property_tree' // specification). void swap(strict_value_holder& value) { holder_.swap(value.holder_); } // Check if current holder is empty (required by // 'boost::property_tree' specification). bool empty() const { return holder_.empty(); } public: // Custom serialization implementation. template <typename Archive> void serialize(Archive& ar, const unsigned int) { using namespace ::boost; // Serialize the value holder. ar & serialization::make_nvp("holder", holder_); } private: // Main value holder instance. Holder holder_; }; // Custom holder-based translator implementation. template <typename T, typename Y> struct value_holder_translator { // Type definitions required by the // 'Translator' concept. typedef T internal_type; typedef Y external_type; boost::optional<Y> get_value(const T& value_holder) const { // Attempt to use casting routine // specific for template type. return value_holder.as<Y>(); } boost::optional<T> put_value(const Y& value) const { // Construct appropriate value holder for specified // value (doesn't throw). return T(value); } }; // Set custom translator for internal 'value_holder' type. template <typename Y> struct translator_between<value_holder, Y> { typedef value_holder_translator<value_holder, Y> type; }; // Set custom translator for internal 'strict_value_holder' type. template <typename Y> struct translator_between<strict_value_holder, Y> { typedef value_holder_translator<strict_value_holder, Y> type; }; } // namespace property_tree // Type definition for custom holder-based properties collection. Note that // for convenience, properties collection is brought to '::boost' namespace. // This kind of properties allows arbitrary parameter types and, therefore, // will substitute unsupported parameter types with dummies when writing. Also // note, that if the parameter structure is filled from some xml-like file, // you will lose the performance of custom value holder, because in this // case parameters are stored as strings (that obviously brings a huge // casting overhead to 'get<non-string-type>' operations). // If you want to save the benefits of type checking and remove type casting // overhead, use serialization instead of raw xml writing / reading. typedef property_tree::basic_ptree< std::string, property_tree::value_holder> properties; // This variation of properties structure supports // wide character keys. typedef property_tree::basic_ptree< std::wstring, property_tree::value_holder> wproperties; // Bring property-specific path declaration into the '::boost' // namespace. using property_tree::path; using property_tree::wpath; // Enable custom tree-like format parsers. using property_tree::xml_parser::write_xml; using property_tree::xml_parser::read_xml; using property_tree::json_parser::write_json; using property_tree::json_parser::read_json; using property_tree::ini_parser::write_ini; using property_tree::ini_parser::read_ini; using property_tree::info_parser::write_info; using property_tree::info_parser::read_info; } // namespace boost 
  • 14
    Do you seriously think that someone will dig into this? - skegg
  • 6
    @mikillskegg I sincerely hope that someone will be able to give adequate comments in general. Well, personally, I would see something like that, I would try. - Costantino Rupert
  • one
    if only comments in Russian! There are no problems, but translating and still reading the code is difficult - sudo97
  • five
    @ Ilya Mikhnevich Well, this is a shame. English is quite primitive here. Sin is not to know and work in IT. - Ukeo
  • 3
    likes to the question from the category: "Wow! I did not understand anything, but it's cool. Layknu" - teanYCH

1 answer 1

The problem is that the very idea of ​​the QSettings class is useless.

The QSettings class provides persistent platform-independent application settings.

What really prevents your Windows program from downloading Linux-style “application settings” from a text file (ini, conf, XML, json, etc), rather than from the registry, using the more common iostream.h library, not the specific Qt library QSettings.h ?

PS With this approach, support is provided for any platform in which there is a file system, and generally there is no need for generalized programming and templates, of which your code consists of 95%.

  • - iostream.h and QSettings.h are not libraries, but headers. There is no one-to-one correspondence between the library and the header. - In reality, my Windows program to do what you wrote about may be hampered by the need to bind settings to different accounts. Or do you like solutions in the style of config_for_Vasya_449.json ? - Costantino Rupert
  • > What really prevents your Windows program from downloading Linux-style “application settings” from a text file (ini, conf, XML, json, etc), and not from the ORLY registry? > If you want to use the INI files, you can pass the QSettings, you can pass the QSettings settings, (QSettings :: IniFormat, QSettings :: UserScope, "MySoft", "Star Runner"); - alopatindev
  • one
    This, of course, is an old question, but: @ Kotik_chocher_kushat in such cases saves something like /home/username/.config/... or /home/username/.myprogramrc . - andrybak
  • On Windows, %APPDATA%/... PS In addition, as far as I know, both options are "extended" to several machines by storing data (including configurations) of the user on the servers. - andrybak
  • You can store all settings in a single configuration file, dividing for each user. When you open the folder - otparsit only what applies to it. - voipp