Boost logo

Boost :

From: Dietmar Kuehl (dietmar_kuehl_at_[hidden])
Date: 2000-09-26 19:06:25


Hi,
Jens Maurer wrote:

> Hm... The widening is locale-dependent. Assuming some stream
> underlying all this, I can call imbue() on it any time. How do I know
> when to invalidate my cached widened characters? Store the locale
> pointer as well and compare the current one with the stored one?

For the implementor of the standard C++ library, there are two
approaches,
for the user of the library only the inferior one:

- The implementation can just add a "cache facet" to the locale object.
  This facet is just initialized last or initialized on request
  using some 'use_facet()' specialization. Unfortunatley, this approach
  is not available to the user. The only approximation is to use facets
  which already cache the values themselves. However, this is not really
  a solid solution.

- The user can cache the values in the 'std::ios_base' passed to the
  formatting functions using the 'pword()'/'iword()' mechanism. Since
  the cache is likely to use allocated memory, a callback for releaseing
  associated memory is necessary anyway which cleans up allocated
  memory on 'copyfmt' and 'erase' events. In addition, the cache can be
  updated if an 'imbue' event is received.

Basically, users don't have a choice but using the second approach. Here
is a brief example of this approach:

  #include <iterator>
  #include <iostream>
  #include <locale>
  #include <cstdlib>

  namespace {
    // allocate an index for the pword()/iword() arrays:
    template <class cT>
    int index() {
      static int const rc = std::ios_base::xalloc();
      return rc;
    }

    // callback to deal with memory release and cache updates:
    template <class cT>
    void callback(std::ios_base::event ev, std::ios_base& fmt,
                  int idx) {
      switch (ev) {
      case std::ios_base::erase_event:
        delete[] static_cast<cT*>(fmt.pword(idx));
        break;
      case std::ios_base::imbue_event:
        delete[] static_cast<cT*>(fmt.pword(idx));
        // fall through
      case std::ios_base::copyfmt_event:
        std::ctype<cT> const& ct
          = std::use_facet<std::ctype<cT> >(fmt.getloc());
        cT* cache = new cT[16];
        char const chars[] = "0123456789abcdef";
        ct.widen(chars, chars + 16, cache);
 fmt.pword(index<cT>()) = cache;
        break;
      }
    }
  }

  // a potential facet member (not a facet member for simplicitiy):
  template <class cT>
  std::ostreambuf_iterator<cT>
  do_format(std::ostreambuf_iterator<cT> to, std::ios_base& fmt,
            unsigned long val) {
    if (fmt.pword(index<cT>()) == 0) {
      std::ctype<cT> const& ct
        = std::use_facet<std::ctype<cT> >(fmt.getloc());
      cT* cache = new cT[16];
      char const chars[] = "0123456789abcdef";
      ct.widen(chars, chars + 16, cache);
      fmt.register_callback(callback<cT>, index<cT>());
      fmt.pword(index<cT>()) = cache;
    }

    // print reverse hexadecimal number (again for simplicity...):
    cT const* cache = static_cast<cT*>(fmt.pword(index<cT>()));
    if (val == 0)
      *to++ = cache[val];
    for (; val > 0; val /= 16)
      *to++ = cache[val % 16];
    return to;
  }

  int main(int ac, char* av[])
  {
    do_format(std::ostreambuf_iterator<char>(std::cout),
              std::cout, ac == 2? std::atoi(av[1]): 0);
    std::ostream out(std::cout.rdbuf());
    out.copyfmt(std::cout);
    do_format(std::ostreambuf_iterator<char>(std::cout),
              std::cout, ac == 2? std::atoi(av[1]): 0);
  }

It ain't pretty but it's effective. What's going on? Well, the initial
processing starts when 'do_format()' is called the first time for a
character type with a certain 'fmt' argument: The function finds that
there is no cache and just creates one using the 'ctype<cT>' facet to
widen the characters. It associates this cache with the 'fmt' object
using the 'pword()' member of 'std::ios_base' which provides access to
a NULL initialized pointer associated with an index.

Since the cache was allocated and might get out of sync with the
'std::locale' object, a callback is registered with the 'fmt' object:
This callback is called in three situations:

- If the object is destroyed, the callback is invoked with the 'event'
  argument set to 'erase_event'.

- If a 'std::locale' object is 'imbue()'ed, the callback is called with
  the 'event' argument set to 'imbue_event'.

- If the 'std::ios_base' member 'copyfmt()' is used, the callback is
  called twice: First, with the 'event' set to 'erase_event' prior to
  any change to clean up the current settings. This is supposed to
  release all memory. Then the formats, locales, etc. are copied and
  the callback is called again, this time with the 'event' set to
  'copyfmt_event'. This pass is supposed to deal with resources now
  shared between the two 'std::ios_base' objects.

The callback in the above example is a template to have the appropriate
character type available which is necessary to release the allocated
memory and to widen to the correct type. For 'erase_event' the memory
is just released. For 'copyfmt_event' the cache is just recreated. The
same is done for 'imbue_event' after releasing the previous cache.

If there are any questions on this, just ask...


__________________________________________________
Do You Yahoo!?
Talk to your friends online with Yahoo! Messenger.
http://im.yahoo.com


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk