Skip to content. Skip to navigation

ICTP Portal

Sections
You are here: Home Manuals on-line PGI Compiler pgC_lib stdlibug 1.6 User-Defined Facets: An Example
Personal tools
Document Actions

1.6 User-Defined Facets: An Example



Click on the banner to return to the user guide home page.

1.6 User-Defined Facets: An Example

The previous sections explained how to use locales and the standard facet classes, and how you can build new facet classes. This section introduces you to the technique of building your own facet class and using it in conjunction with the input/output streams of the Standard C++ Library, the iostreams. This material is rather advanced, and requires some knowledge of standard iostreams.

In the following pages, we will work through a complete example on formatting telephone numbers. Formatting telephone numbers involves local conventions that vary from culture to culture. For example, the same US phone number can have all of the formats listed below:

754-3010
Local
(541) 754-3010
Domestic
+1-541-754-3010
International
1-541-754-3010
Dialed in the US
001-541-754-3010
Dialed from Germany
191 541 754 3010
Dialed from France

Now consider a German phone number. Although a German phone number consists of an area code and an extension like a US number, the format is different. Here is the same German phone number in a variety of formats:

636-48018
Local
(089) / 636-48018
Domestic
+49-89-636-48018
International
19-49-89-636-48018
Dialed from France

Note the difference in formatting domestic numbers. In the US, the convention is 1 (area code) extension, while in Germany it is (0 area code)/extension.

1.6.1 A Phone Number Class

An application that has to handle phone numbers will probably have a class that represents a phone number. We will also want to read and write telephone numbers via iostreams, and therefore define suitable extractor and inserter functions. For the sake of simplicity, we will focus on the inserter function in our example.

To begin, here is the complete class declaration for the telephone number class phoneNo:

class phoneNo
{
public:
 typedef basic_ostream<char> outStream_t;
 typedef string string_t;

 phoneNo(const string_t& cc,const string_t& ac,const string_t& ex)
    : countryCode(cc), areaCode(ac), extension(ex) {}

private:
   string_t countryCode;                                     //"de"
   string_t areaCode;                                        //"89"
   string_t extension;                                //"636-48018"

friend phoneNo::outStream_t& operator<<
   (phoneNo::outStream_t&, const phoneNo&);
};

1.6.2 A Phone Number Formatting Facet Class

Now that we have locales and facets in C++, we can encapsulate the locale-dependent parsing and formatting of telephone numbers into a new facet class. Let's focus on formatting in this example. We will call the new facet class phone_put, analogous to time_put, money_put, etc.

The phone_put facet class serves solely as a base class for facet classes that actually implement the locale-dependent formatting. The relationship of class phone_put to the other facet classes is illustrated in Figure 14:

Figure 14. The relationship of the phone_put facet to the implementing facets


Here is a first tentative declaration of the new facet class phone_put:

class phone_put: public locale::facet                        //1
{
public:
   static locale::id id;                                     //2
   phone_put(size_t refs = 0) : locale::facet(refs) { }      //3

   string_t put(const string_t& ext
               ,const string_t& area 
               ,const string_t& cnt) const;                  //4
};
//1Derive from the base class locale::facet, so that a locale object will be able to maintain instances of our new phone facet class.
//2New facet classes need to define a static data member id of type locale::id.
//3Define a constructor that takes the reference count that will be handed over to the base class.
//4Define a function put() that does the actual formatting.

1.6.3 An Inserter for Phone Numbers

Now let's take a look at the implementation of the inserter for our phone number class:

ostream& operator<<(ostream& os, const phoneNo& pn)
{ 
 locale loc = os.getloc();                                    //1
 const phone_put& ppFacet = use_facet<phone_put> (loc);       //2
 os << ppFacet.put(pn.extension, pn.areaCode, pn.countryCode);//3
 return (os);
}
//1The inserter function will use the output stream's locale object (obtained via getloc()),
//2use the locale's phone number facet object,
//3and call the facet object's formatting service put().

1.6.4 The Phone Number Facet Class Revisited

Let us now try to implement the phone number facet class. What does this facet need to know?

  • A facet needs to know its own locality, because a phone number is formatted differently for domestic and international use; for example, a German number looks like (089) / 636-48018 when used in Germany, but it looks like +1-49-89-636-48018 when used internationally.

  • A facet needs information about the prefix for dialing international numbers; for example, 011 for dialing foreign numbers from the US, or 00 from Germany, or 19 from France.

  • A facet needs access to a table of all country codes, so that one can enter a mnemonic for the country instead of looking up the respective country code. For example, I would like to say: "This is a phone number somewhere in Japan" without having to know what the country code for Japan is.

1.6.4.1 Adding Data Members

The following class declaration for the telephone number formatting facet class is enhanced with data members for the facet object's own locality, and its prefix for international calls (see //2 and //3 in the code below). Adding a table of country codes is omitted for the time being.

class phone_put: public locale::facet {
public:
  typedef string string_t;
  static locale::id id;
  phone_put(size_t refs = 0) : locale::facet(refs)
                             , myCountryCode_("")
                             , intlPrefix_("")      {  }
 string_t put(const string_t& ext, 
              const string_t& area, 
              const string_t& cnt) const;
protected:
  phone_put( const string_t& myC                              //1
           , const string_t& intlP
           , size_t refs = 0)
           : locale::facet(refs)
           , myCountryCode_(myC)
           , intlPrefix_(intlP)   {  }
  const string_t myCountryCode_;                              //2
  const string_t intlPrefix_;                                 //3
};

Note how this class serves as a base class for the facet classes that really implement a locale-dependent phone number formatting. Hence, the public constructor does not need to be extended, and a protected constructor is added instead (see //1 above).

1.6.4.2 Adding Country Codes

Let us now deal with the problem of adding the international country codes that were omitted from the previous class declaration. These country codes can be held as a map of strings that associates the country code with a mnemonic for the country's name, as shown in Figure 15:

Figure 15. Map associating country codes with mnemonics for countries' names


In the following code, we add the table of country codes:

class phone_put: public locale::facet 
{
 public:
   class prefixMap_t : public map<string,string>             //1
   { 
    public:
       prefixMap_t() { insert(tab_t(string("US"),string("1"))); 
       insert(tab_t(string("De"),string("49")));
       // ...
   }
};
   static const prefixMap_t* std_codes()                     //2
          { return &stdCodes_; }
 protected:
   static const prefixMap_t stdCodes_;                       //3
};

As the table of country codes is a constant table that is valid for all telephone number facet objects, it is added as a static data member stdCodes_ (see //3). The initialization of this data member is encapsulated in a class, prefixMap_t (see //1). For convenience, a function std_codes() is added to give access to the table (see //2).

Despite its appealing simplicity, however, having just one static country code table might prove too inflexible. Consider that mnemonics might vary from one locale to another due to different languages. Maybe mnemonics are not called for, and you really need more extended names associated with the actual country code.

In order to provide more flexibility, we can build in the ability to work with an arbitrary table. A pointer to the respective country code table can be provided when a facet object is constructed. The static table, shown in Figure 16 below, will serve as a default:

Figure 16. Map associating country codes with country names


Since we hold the table as a pointer, we need to pay attention to memory management for the table pointed to. We will use a flag for determining whether the provided table needs to be deleted when the facet is destroyed. The following code demonstrates use of the table and its associated flag:

class phone_put: public locale::facet {
public:
  typedef string string_t;
  class prefixMap_t;
  static locale::id id;

  phone_put( const prefixMap_t* tab=0                         //1
           , bool del = false
           , size_t refs = 0)
           : locale::facet(refs)
           , countryCodes_(tab), delete_it_(del)
           , myCountryCode_(""), intlPrefix_("") 
  { if (tab)   {  countryCodes_ = tab;
                  delete_it_ = del;   }
    else       {  countryCodes_ = &stdCodes_;                 //2
                  delete_it_ = false; }
  }
  string_t put(const string_t& ext, 
               const string_t& area, 
               const string_t& cnt) const;

  const prefixMap_t* country_codes() const                    //3
  { return countryCodes_; }

  static const prefixMap_t* std_codes()    { return &stdCodes_; }
protected:
  phone_put(const string_t& myC, const string_t& intlP
          , const prefixMap_t* tab=0, bool del = false
          , size_t refs = 0)
          : locale::facet(refs)
          , countryCodes_(tab), delete_it_(del)
          , myCountryCode_(myC), intlPrefix_(intlP) 
  { ... }
  virtual ~phone_put() 
  { if(delete_it_)
      countryCodes_->prefixMap_t::~prefixMap_t();             //4
  }

  const prefixMap_t* countryCodes_;                           //5
  bool delete_it_;                                               
  static const prefixMap_t stdCodes_;
  const string_t myCountryCode_;
  const string_t intlPrefix_;
};
//1The constructor is enhanced to take a pointer to the country code table, together with the flag for memory management of the provided table.
//2If no table is provided, the static table is installed as a default.
//3For convenience, a function that returns a pointer to the current table is added.
//4The table is deleted if the memory management flags says so.
//5Protected data members are added to hold the pointer to the current country code table, as well as the associated memory management flag.

1.6.5 An Example of a Concrete Facet Class

As mentioned previously, the phone number facet class is intended to serve as a base class. Let's now present an example of a concrete facet class, the US phone number formatting facet. It works by default with the static country code table and "US" as its own locality. It also knows the prefix for dialing foreign numbers from the US. Here is the class declaration for the facet:

class US_phone_put : public phone_put {
public:
   US_phone_put( const prefixMap_t* tab=0
               , const string_t& myCod = "US"
               , bool del = false
               , size_t refs = 0)
               : phone_put(myCod,"011",tab,del,refs)
   { }
};

Other concrete facet classes are built similarly.

1.6.6 Using Phone Number Facets

Now that we have laid the groundwork, we will soon be ready to format phone numbers. Here is an example of how instances of the new facet class can be used:

ostream ofstr("/tmp/out");
ostr.imbue(locale(locale::classic(),new US_phone_put));       //1
ostr << phoneNo("Fr","1","60 17 07 16") << endl;
ostr << phoneNo("US","541","711-PARK") << endl;

ostr.imbue(locale(locale("Fr")                                //2
          ,new Fr_phone_put (&myTab,"France")));
ostr << phoneNo("Allemagne","89","636-40938") << endl;        //3
//1Imbue an output stream with a locale object that has a phone number facet object. In the example above, it is the US English ASCII locale with a US phone number facet, and
//2a French locale using a French phone number facet with a particular country code table.
//3Output phone numbers using the inserter function.
    The output will be: 011-33-1-60170716
    (541)711-PARK
    19 49 89 636 40938

1.6.7 Formatting Phone Numbers

Even now, however, the implementation of our facet class is incomplete. We still need to mention how the actual formatting of a phone number will be implemented. In the example below, it is done by calling two virtual functions, put_country_code() and put_domestic_area_code():

class phone_put: public locale::facet {
public:
  // _
  string put(const string& ext, 
             const string& area, 
             const string& cnt) const;
protected:
  // _
  virtual string_t put_country_code
          (const string_t& country) const = 0;
  virtual string_t put_domestic_area_code
          (const string_t& area) const = 0;
};

Note that the functions put_country_code() and put_domestic_area_code() are purely virtual in the base class, and thus must be provided by the derived facet classes. For the sake of brevity, we spare you here the details of the functions of the derived classes. For more information, please consult the directory of sample code delivered on disk with this product.

1.6.8 Improving the Inserter Function

Let's turn here to improving our inserter function. Consider that the country code table might be huge, and access to a country code might turn out to be a time-consuming operation. We can optimize the inserter function's performance by caching the country code table, so that we can access it directly and thus reduce performance overhead.

1.6.8.1 Primitive Caching

The code below does some primitive caching. It takes the phone facet object from the stream's locale object and copies the country code table into a static variable.

ostream& operator<<(ostream& os, const phoneNo& pn)
{
   locale loc = os.getloc();
   const phone_put& ppFacet = use_facet<phone_put> (loc);
  
   // primitive caching
   static prefixMap_t codes = *(ppFacet.country_codes());

   // some sophisticated output using the cached codes
   ...
   return (os);
}

Now consider that the locale object imbued on a stream might change, but the cached static country code table does not. The cache is filled once, and all changes to the stream's locale object have no effect on this inserter function's cache. That's probably not what we want. What we do need is some kind of notification each time a new locale object is imbued, so that we can update the cache.

1.6.8.2 Registration of a Callback Function

In the following example, notification is provided by a callback function. The iostreams allow registration of callback functions. Class ios_base declares:

enum event { erase_event, imbue_event, copyfmt_event };       //1
typedef void (*event_callback) (event, ios_base&, int index);
void register_callback (event_callback fn, int index);        //2
//1Registered callback functions are called for three events:

  • Destruction of a stream,

  • Imbuing a new locale, and

  • Copying the stream state.

//2The register_callback() function registers a callback function and an index to the stream's parray. During calls to imbue(), copyfmt() or ~ios_base(), the function fn is called with argument index. Functions registered are called when an event occurs, in opposite order of registration.

The parray is a static array in base class ios_base. One can obtain an index to this array via xalloc(), and access the array via pword(index) or iword(index), as shown in Figure 17 below:

    Figure 17. The static array parray


In order to install a callback function that updates our cache, we implement a class that retrieves an index to parray and creates the cache, then registers the callback function in its constructor. The procedure is shown in the code below:

class registerCallback_t {
public:
registerCallback_t(ostream& os
                  ,ios_base::event_callback fct
                  ,prefixMap_t* codes)
  {
   int index = os.xalloc();                                   //1
   os.pword(index) = codes;                                   //2
   os.register_callback(fct,index);                           //3
  }
};
//1An index to the array is obtained via xalloc().
//2The pointer to the code table is stored in the array via pword().
//3The callback function and the index are registered.

The actual callback function will later have access to the cache via the index to parray.

At this point, we still need a callback function that updates the cache each time the stream's locale is replaced. Such a callback function could look like this:

void cacheCountryCodes(ios_base::event event
                      ,ios_base& str,int cache)
{  if (event == ios_base::imbue_event)                        //1
   {
      locale loc = str.getloc();
      const phone_put<char>& ppFacet = 
                   use_facet<phone_put<char> > (loc);         //2

      *((phone_put::prefixMap_t*) str.pword(cache)) = 
                   *(ppFacet.country_codes());                //3
   }
}
//1It checks whether the event was a change of the imbued locale,
//2retrieves the phone number facet from the stream's locale, and
//3stores the country code table in the cache. The cache is accessible via the stream's parray.

1.6.8.3 Improving the Inserter

We now have everything we need to improve our inserter. It registers a callback function that will update the cache whenever necessary. Registration is done only once, by declaring a static variable of class registerCallback_t.

ostream& operator<<(ostream& os, const phoneNo& pn)
{
 static phone_put::prefixMap_t codes =
    *(use_facet<phone_put>(os.getloc()).country_codes());     //1

 static registerCallback_t cache(os,cacheCountryCodes,&codes);//2

  // some sophisticated output using the cached codes
  ...
}
//1The current country code table is cached.
//2The callback function cacheCountryCodes is registered.


©Copyright 1996, Rogue Wave Software, Inc.


Powered by Plone This site conforms to the following standards: