Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Factory Redux, Part 1


Conversations: Factory Redux, Part 1

"Hey, have you been up to the factory lately?" Wendy poked her head into my cubicle. "They've set up a new surface-mount production line that can handle more complex boards." Being less into embedded programming than Wendy was and, therefore, less in tune with the hardware engineering department of our company, I understood this news only slightly better than a tribal chief in the deep, dark Amazon rain forest might understand the significance of who won the baseball All-Star game.

"With the older machines," she patiently explained, "we were pretty limited in what we could do. The new machines are more flexible and let us do a wider variety of printed circuit boards."

"I wish my factory were that flexible," I sighed. Wendy cocked her eyebrow quizzically. "You remember the templated Factory Method pattern that the Guru showed me a while back?"[1]

"Oh, right," Wendy nodded, "then you figured out how to use that factory to save objects to a file.[2] So what's the problem?"

"Well, the factory worked great, as long as you use nothing but default constructors. The creation function accepts no parameters. But then I read Alexandrescu's chapters on factories -- there's a lot of good stuff in that book -- and I made the creation function a template argument[3]:

template
<class AncestorType,
 typename classIDKey =
   defaultIDKeyType,
 typename BaseCreateFn =
   auto_ptr< AncestorType >
    (*)()
>
class genericFactory
{
"And that works great, because now I can use different sets of parameters for different class hierarchies, like this:"

auto_ptr< Widget > widget =
  genericFactory< Widget >::
    instance().create();
typedef auto_ptr< Gadget >
  ( * GadgetCreator )( int );
auto_ptr< Gadget > gadget =
  genericFactory<
    Gadget,
    string,
    GadgetCreator
  >::instance().create( 42 );
"I sense a 'but...' here," Wendy prompted.

"Well, yeah," I sighed. "This approach works great, as long as all the classes in the hierarchy accept the same number and type of parameters in their constructors. All objects derived from Gadget have to take an int parameter in their constructor. I'm running into more and more classes that can't be default constructed, and where the derived classes take a different set of arguments than their parents, like:"

class Base
{
public:
  Base( int );
};
class Derived : public Base
{
public:
  Derived( string );
};
"Well, what about a parallel hierarchy?" Wendy asked as she took over at the keyboard:

class Base
{
public:
  struct BaseParams
  {
    int value_;
  };
  Base( int );
  Base( const BaseParams & );
};
class Derived : public Base
{
  string word_;
public:
  struct DerivedParams :
    public BaseParams
  {
    string word_;
  };
  Derived( string );
  Derived( const DerivedParams & dp )
  : Base( dp ), word_( dp.word_ )
  {}
};
"Now, each class takes a single parameter, which can be customized for each class in the hierarchy. And, since the parameter uses inheritance, it can be easily handled by the factory."

"Very good, my daughter." Once again, I jumped at the Guru's voice in my ear. "But this is one step into a larger world. The challenge now, my children, is to take this scheme and make it generic."

"Generic in what way?" I asked. "The factory is already generic, and with this new parallel hierarchy it will probably serve without modifications for a long time."

"True, my apprentice, the factory is generic," the Guru replied. "But what of the class itself? Show me how you would implement the actual save and restore functions."

I turned to my keyboard. Having just been presented with the parallel inheritance scheme, I hadn't had any chance to work out the details.

"Ah, well," I stalled for time, "I guess the BaseParams class is pretty much a value type, which can be default constructed. I can use the original factory to save and restore it:"

struct BaseParams
{
  int value_;
  void save( ostream & ) const;
  void restore( istream &);
  virtual void do_save( ostream &);
"And that, my child, is the challenge," the Guru interrupted. "How can you make the persistence code more general, so that the object does not care where it is being stored? Suppose, for example, that the next project stores the object in a database. How can this be structured so that you do not need to modify the class?"

"Oh, I see. Ah...well...it's easy," I lied. "I'd just...um...I dunno."

"Worry not, my child," the Guru said. She paused as Bob passed by, scowling, on his way out the door. After he was gone, it was amazing to watch the transformation as she relaxed, dropping the Guru act.

"It's OK," she said, "I didn't expect you to know. Let's start with the framework that the classes will need. The template factory that you created is, when boiled down to its essence, a mechanism for associating a token with a pointer-to-a-function. Let's extend the factory to associate the token with two functions: one to create the objects and one to save the objects. We need to set up the default parameters so that existing code does not break."[4] She sat down at the keyboard, and started typing:

class DoNothingClass
{
};
template
<
  class AncestorType,
  typename classIDKey =
     defaultIDKeyType,
  typename Source=DoNothingClass,
  typename Sink = DoNothingClass,
  typename Parameter =
     DoNothingClass,
  typename ConcreteCreateFn =
     auto_ptr<AncestorType>
     (*)( ),
  typename ConcreteSaveFn =
     void (*)( Sink &,
             const Parameter & )
>
class GenericFactory
{
  struct Functions
  {
    ConcreteCreateFn creator;
    ConcreteSaveFn saver;
  };
  typedef map<classIDKey,
    Functions> FN_REGISTRY;
  FN_REGISTRY registry;
public:
  void registerFunctions(
    const classIDKey & clName,
    ConcreteCreateFn creator,
    ConcreteSaveFn saver = NULL)
  {
    Functions funcs;
    funcs.creator = creator;
    funcs.saver = saver;
    registry[clName]=funcs;
  }
"The registerFunctions member," the Guru explained as she typed, "is very much like the original function -- it creates the association between the class identifier and the create and save functions."

  auto_ptr<AncestorType>
  create(
    const classIDKey &className,
    Source & source ) const
  {
    auto_ptr<AncestorType> ret(0);
    typename
      FN_REGISTRY::const_iterator
      regEntry =
        registry.find(className);
    if ( regEntry !=
         registry.end())
    {
      auto_ptr<AncestorType>
        tmp(
          (*regEntry).second.
            creator( source )
        );
      ret=tmp;
    }
    return ret;
  }
"The create function is pretty much the same as your original, except that it accepts an argument from which the class will be created, and passes that argument on to the creation function."

  void save(
    const classIDKey &className,
    Sink &sink,
    Parameter & param
  ) const
  {
    typename
      FN_REGISTRY::const_iterator
        regEntry =
          registry.find(className);
    if ( regEntry !=
         registry.end())
    {
      (*regEntry).second.saver(
                   sink,
                   param );
    }
  }
"The save function is the new one. This function looks up the specified class identifier, and if the class is registered, it calls the associated save function.

"Now, back to my original question: how to isolate the class hierarchy from the saving mechanism." I gave her my tried-and-trusted deer-in-the-headlights look. She sighed. "Let's work this up from first principles. How do you write generic code?"

"Templates," I said immediately, glad to be able to answer something.

"Exactly. Let's start with a simple template, which we'll add to the factory's header file:"

template
<
  typename T,
  typename Storage
>
class PersistentStorage
{
public:
  explicit PersistentStorage(
      Storage & s );
  T & restore();
  void save( const T & );
};
"This template will be used inside the function that creates the object, that is, the function that gets registered as the creator parameter in GenericFactory::registerFunctions.

"The next step is to plug this into the helper classes you wrote for your factory, and extend the helper class to make the istream and ostream into template arguments." She called up the original code on my monitor. "The existing registerInFactory template will work fine for objects that can be default constructed, so we'll add a new class template:"

template
<
  class AncestorType,
  typename AncestorParameter,
  class ConcreteType,
  typename ConcreteParameter,
  typename Source,
  typename Sink,
  typename ConcreteCreateFn =
    auto_ptr<AncestorType>
    (*)( Source & ),
  typename classIDKey =
    defaultIDKeyType
>
class RegisterInFactoryWithParams
{
public:
  static auto_ptr< AncestorType >
  createInstance( Source & source )
  {
    PersistentStorage
    <
      ConcreteParameter,
      Source
    > storage( source );
    return auto_ptr< AncestorType >(
      new ConcreteType(
        storage.restore()
      )
    );
  }
"The createInstance function instantiates the template, then calls the restore() function to retrieve the object that was stored. The newly created object is then initialized with the resulting ConcreteParameter object."

"Wait a second," I interrupted. "How does the PersistentStorage template know how to retrieve the object? It still doesn't know what kind of object it's dealing with."

"That is true," the Guru said, "but you'll notice that the PersistentStorage functions in the base template have no implementations. The PersistentStorage template can never be instantiated as-is, you must provide partial or full specializations of the template. That is part of the storage-specific implementation you need to write. At this moment, though, I'm just building the framework. Next we have the static member function that saves the object:"

  static void saveInstance(
    Sink & sink,
    const AncestorParameter & ap )
  {
    PersistentStorage
    <
      ConcreteParameter,
      Sink
    > storage( sink );
    storage.save(
      static_cast
      <
        const ConcreteParameter &
      > ( ap )
    );
  }
"And finally, the glue that binds it into the factory: the constructor for the RegisterInFactoryWithParams template.

  RegisterInFactoryWithParams(
     const classIDKey &id )
  {
    GenericFactory
    <
      AncestorType,
      classIDKey,
      Source,
      Sink,
      AncestorParameter,
      ConcreteCreateFn
    >
    ::instance().registerFunctions(
        id,
        createInstance,
        saveInstance );
  }
};
"As with your original helper, the constructor instantiates the factory, and registers the static create and save functions in the factory. The framework is now complete, ready for you to plug your class hierarchy into it."

"This is the part I'm waiting to see," Wendy piped up. I had forgotten she was watching. The Guru started to speak, but my stomach interrupted with a loud rumbling.

"Saved by the bell," the Guru said. "I may be thin, but I need to eat too. We'll continue after lunch."

Next: how to incorporate the PersistentStorage<> framework into your class hierarchy, and a summary of the pros and cons of this approach.

Acknowledgments

Our thanks to the many readers who have sent comments on the previous version of the factory, pointing out flaws and some portability issues. Our thanks also to Scott Meyers, whose comments about "default-constructors-always-exist lemmings" forced a re-think of the design to accommodate a wider variety of constructors.

References

1. "Conversations: Abstract Template, Factory Style," C/C++ Users Journal Experts Forum, June 2001, http://www.cuj.com/experts/1906/hyslop.htm.

2. "Conversations: How To Persist an Object," C/C++ Users Journal Experts Forum, July 2001, http://www.cuj.com/experts/1907/hyslop.htm.

3. Modern C++ Design: Generic Programming and Design Patterns Applied. Andrei Alexandrescu, Addison-Wesley, 2001.

4. For brevity, only those functions that are new or changed from the original factory are shown here. For details on the full factory, see [1] or the sample code available at http://www.cuj.com/code.

About the Authors

Herb Sutter(<www.gotw.ca>) is convener of the ISO C++ standards committee, author of the acclaimed books Exceptional C++ and More Exceptional C++, and one of the instructors of The C++ Seminar (<www.gotw.ca/cpp_seminar>). In addition to his independent writing and consulting, he is also C++ community liaison for Microsoft.

Jim Hyslop is a senior software designer with over 10 years programming experience in C and C++. Jim works at Leitch Technology International Inc., where he deals with a variety of applications, ranging from embedded applications to Windows programs. He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.