Persistant Properties With C++ - the Readable Way

.

Environment: VC6

Introduction

During my many years of C++ programming I ran into so many cases where there was a collection of persistant properties to be maintained by means of system registry, INI files, database tables etc. etc., and as a fanatic for clear and readable code I always hated to see how my code get cluttered each time I wanted to set a new property or query for a previously saved one.

When STL first came out it was fascinating to see how easy it was to set a new value in a map container and query for it later on, just by using the [] operator. I mean, if I could only do things like:

CDatabaseTable dbt("my_table");
dbt["Name"] = "Joe Shmoe";
cout << dbt["Name"] << endl;

...or...

CRegistry r(HKEY_CURRENT_USER, "Software\\MyApp\\Settings");
r["window_x"] = 112;
r["window_y"] = 200;

After all, if you leave alone the read and write that fit each storage nature, this is all very much the same thing, wouldn't you agree ?

To make the long story short, I needed the means to work with any dictionary-like storage by using the [] operator for both read and write. Nothing too fancy, I agree, but certainly a life saver for a readable-code freak like myself!

So Here It Is...

template <class Key, class Value>
class CPropertiesStorage
{
public :
  class CPropertiesStoragePair
  {
  public :
    Key first;
    Value second;

    CPropertiesStoragePair(CPropertiesStorage& stg)
      : m_storage(stg) {}
    CPropertiesStoragePair(CPropertiesStorage& stg, 
                           const Key& k)
      : m_storage(stg), first(k) {}
    CPropertiesStoragePair(CPropertiesStorage& stg, 
                           const Key& k, 
                           const Value& v)
      : m_storage(stg), first(k), second(v) {}
    CPropertiesStoragePair(const CPropertiesStoragePair& sp)
      : m_storage(sp.m_storage), first(sp.first), 
              second(sp.second) {}

    CPropertiesStoragePair& operator = (const Value& v)
    {
      second = v;
      m_storage.Write(first, second);
      return *this;
    }

    CPropertiesStoragePair& operator = 
               (const CPropertiesStoragePair& sp) const
    {
      if( &sp != this )
      {
        m_storage = sp.m_storage;
        first = sp.first;
        second = sp.second;
      }
      return *this;
    }

    operator const Value&() const
    {
      return (const Value&)second;
    }

  private :
    CPropertiesStorage& m_storage;
  };
  friend class CPropertiesStoragePair;

  CPropertiesStorage() {}
  virtual ~CPropertiesStorage() {}

  CPropertiesStoragePair operator[] (const Key& k)
  {
    CPropertiesStoragePair sp(*this, k);
    Read(sp.first, sp.second);
    return sp;
  }

protected :
  virtual void Read(const Key& k, Value& v) = 0;
  virtual void Write(const Key& k, const Value& v) = 0;
};

There are two template parameters, the key type and the value type. This is just like what you should already know from your STL experience. The only thing that is left is two pure-virual methods where you can implement the actual read and write mechanism according to the storage you're using. I've also decided not to return any result form those two functions because at that stage I can't be totally sure of how you might implement those two methods. I figured that the actual implementation would throw some kind of exception it something bad happens.

Sample

Here is a sample code that uses an INI file as the storage media:

class CIniFileSection 
    : public CPropertiesStorage<std::string, std::string>
{
public :
  CIniFileSection(const char* lpszIniFileName, 
                  const char* lpszSection)
    : m_sIniFileName(lpszIniFileName), 
                     m_sSection(lpszSection)
  {
  }

protected :
  const std::string m_sIniFileName;
  const std::string m_sSection;

  virtual void Read(const std::string& k, std::string& v)
  {
    char szBuf[200];
    GetPrivateProfileString(m_sSection.c_str(), 
                            k.c_str(), 
                            "", 
                            szBuf, sizeof(szBuf), 
                            m_sIniFileName.c_str());
    v = szBuf;
  }

  virtual void Write(const std::string& k, 
                     const std::string& v)
  {
    WritePrivateProfileString(m_sSection.c_str(), 
                              k.c_str(), 
                              v.c_str(), 
                              m_sIniFileName.c_str());
  }
};

int main()
{
  CIniFileSection r("d:\\test.ini", "main");

  r["key1"] = "value1";

  std::cout << r["key1"].second << std::endl;

  return 0;
}

Finally...

What I'm presenting here is an idea. If any of you have a better way to do what I have tried to do, please let me know.