9
\$\begingroup\$

This is a small extension based on a boost::property_tree, which supports arbitrary values for the properties and appropriate serialization. It can be used as an alternative to QSettings in Qt and provides very convenient way to pass properties inside your applications.

I would like to have a code review from somebody, just to point out what I might be doing wrong, because I'm not that good at C++ coding.

#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
\$\endgroup\$
  • \$\begingroup\$ Looks interesting. Maybe add some comments or test case to illustrate how it is used and what it bring over boost (if it is pertinent) would be great. Your coding style looks pretty nice though (I didn't have time to check in details). \$\endgroup\$ – n1ckp Apr 26 '11 at 21:43
  • \$\begingroup\$ To be honestly - I would like to use your extension, but I can't because of missing files core_defines.h and core_extensions_lexical_cast.h. Could you share a minimum required for compilation? Thanks in advance! \$\endgroup\$ – user4234 May 12 '11 at 6:44
3
\$\begingroup\$

I think use boost::serialize is not are good idea. I read about this library (not all Boost) and they have problem compatibility with previous versions.

I wrote other property container and property manager some times ago. And we use template class, without Boost, like this:

//Contains one property any complexity you want
template <typename Type>
class Property : public PropertyBase
{
protected:
    Type m_property;
public:
    /**
        for deep copy you must define copy c-tor
        @param Type const& prop - any property
    **/
    Property(Type const& prop)
    :m_property(prop)
    {
    }
    virtual ~Property() { }
public:
    // @return Type const& - contained property
    Type const& GetProperty() const { return m_property; }

    /**
        for deep copy you must define operator=
        @param Type const& prop - any property
    **/
    void SetProperty(Type const& prop)
    {
        m_property = prop;
    }
};

typedef PointerContainer<int, PropertyBase> TProperties; //list of properties for group
typedef PointerContainer<int, TProperties>  TPropertyGroupList; //list of properties groups

//Class for manipulation with properies
class BasePropertyProvider
{
protected:
    TPropertyGroupList  m_propertyGroupList; // container of all properties
.................
public:
    /**
        @param int propertyGroup - group of properties for which we search 
        @param int propertyType - type of property which we search
        @return Type const* - pointer on value or 0 if searching failed
    **/
    template <typename Type>
    Type const* GetProperty(int propertyGroup, int propertyType) const;

    /**
        Add property in inner storage
        @param int propertyGroup - group of properties for add
        @param int propertyType - type of property for add
        @param Type const & - reference on value for add
    **/
    template <typename Type>
    void AddProperty(int propertyGroup, int propertyType, Type const &prop);
};

It's not full realization, but I think you understand the direction.

Also I know Qt solution property. You can find it here.

But they're really heavy tools, and if you want to add some original property you must write 3 or 4 classes.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.