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 2


Conversations: Factory Redux, Part 2

After we had all returned from lunch, we –– the Guru, Wendy, and I –– gathered at my cubicle to go over the rest of the persistence framework.

"This morning," the Guru began, "we finished the factory. Now that the framework is complete, we can plug your class hierarchy into it. Suppose we want to write code that uses streams for persistence. In your source code that deals with persistence for the Base class to and from streams, you register the functions by instantiating a RegisterInFactory object, specifying istream and ostream as template arguments for the Source and Sink:"

namespace {
  RegisterInFactoryWithParams
  < 
    Base, 
    Base::BaseParams, 
    Base, 
    Base::BaseParams, 
    istream, 
    ostream
  >
  registerBase( Base::baseID() );
}
"We're using the BaseParams struct you came up with," the Guru nodded at Wendy. "The class hierarchy is not entirely isolated from the persistence framework. We can create new objects easily enough without intruding into the hierarchy, but the base class will have to provide a means of saving the required information."

"I'll bet I know how this is going to work," I said. I took over the keyboard, and modified the Base class:

class Base
{
  template< typename Sink >
  virtual void write( Sink & o ) const;
  
"That would be the precise implementation," the Guru said, "but you've overlooked one minor point: Member template functions cannot be virtual, so I'm afraid it's going to have to be a little more roundabout than that."

"Why is that? Why can't template functions be virtual?" I wondered aloud.

"That will have to wait for another conversation," the Guru smiled enigmatically. She erased what I wrote. "We will, of course, need a virtual function of some sort, so that the persistence framework can deal only with the base class. The virtual function will return an auto_ptr to a BaseParams class. The contents of that class can then be passed to the PersistentStorage<> framework for saving."

class Base
{
public:
  struct BaseParams
  {
    int value_;
  };

  virtual auto_ptr< BaseParams >
    retrieveStorageInfo() const;
"We also need to provide a function to perform the writing. We can use a member-function template to keep it generic, and not tied to streams."
  template< 
    typename Source,
    typename Sink,
    typename RestoreFn,
    typename SaveFn  >
  void write( Sink & o ) const
  {
    PersistentStorage
    < 
      string, 
      Sink 
    >( o ).save( classID() );
    GenericFactory
    <
      Base,
      BaseKeyType,
      Source,
      Sink,
      BaseParams,
      RestoreFn,
      SaveFn
    >::instance().save( 
        classID(), 
        o, 
        *retrieveStorageInfo()
    );
  }
 
  virtual BaseKeyType
    classID() const;
};
"That's it, we're done," she said.

"Er, not quite," I said. "How does my program actually perform the saving and restoring?" The Guru did not reply, but simply pushed the keyboard over to me. I thought for a moment, then decided to start with the saving:

typedef auto_ptr< Base >
   (* BaseStreamRestoreFn)
   ( istream & ); 
typedef void 
   (*BaseStreamSaveFn)
   ( ostream &, 
     const Base::BaseParams & );

void saveBaseToStream(
    Base &b, ostream & o)
{
  b.write< 
    istream, 
    ostream,
    BaseStreamRestoreFn,
    BaseStreamSaveFn
  >( o );
}
The Guru nodded her approval. "Very good. Now walk me through the save process."

"The saveBaseToStream() function calls the instantiation of the write member function. The Base::write member function first writes the class's type. Then it calls GenericFactory::save(), passing it the class type, the output object (in this case, ostream), and the parameters to save. The class type is retrieved from the virtual classId() function, the output is specified in the member-template function instantiation as istream, and the parameter is retrieved via the virtual function retrieveStorageInfo().

"GenericFactory::save, which you wrote this morning, looks up the class type in its registry, and invokes the save function registered with it. In the case of a Base class, it calls registerBase.saveInstance(). saveInstance() instantiates a PersistentStorage< Base::BaseParams, ostream > object, and calls its save() function. The... um... the PersistentStorage... uh, we forgot to specialize that."

"Quite so," the Guru said calmly. "Write a specialization for it now. Make it generic as well, not tied to ostream." I thought for a moment.

template< typename Storage >
class PersistentStorage<
  Base::BaseParams, Storage 
>
{
public:
  explicit PersistentStorage( 
    Storage & os )
    : warehouse_( os )
  {
  }
  void save( 
    const Base::BaseParams & bp 
  ) const
  {
    PersistentStorage< int, 
      Storage >
    ( warehouse_ ).save( bp.value_ );
  }
protected:
    Storage & warehouse_;
private:
    Base::BaseParams param_;
};
"Right, we're cooking now," I said. "The PersistentStorage< Base::BaseParams, Storage > partial specialization will use specializations for the int member. I'll create a specialization for PersistentStorage< int, std::ostream >, and in its save() function I'll insert the value into the stream."

"Well done, my child," the Guru dropped momentarily back in her act, as Bob walked by on his way to get a latte. "My daughter," she nodded at Wendy, "would you care to write the restore part of the picture?" Wendy slid in at the keyboard.

"First, I'm going to provide a typedef," she said as she tapped at the keyboard.

"Getting a little lazy in your old age?" I knew Wendy had just celebrated her birthday, and was a little sensitive about that topic.

"Watch it, junior," she said in her best imitation of Bob. "There's another reason for the typedef –– the factory takes a lot of parameters, and you don't want to risk getting some of them wrong."

typedef GenericFactory< 
        Base, 
        string, 
        istream, 
        ostream,
        Base::BaseParams, 
        BaseStreamRestoreFn,
        BaseStreamSaveFn
        > 
BaseFactoryUsingStreams;

auto_ptr<Base>
restoreBaseFromStream( 
    istream &i )
{
  BaseFactoryUsingStreams & 
    factory =
    BaseFactoryUsingStreams::
      instance();
  string className = 
    PersistentStorage
    <
      string,
      istream
    >( infile ).restore();
  auto_ptr<Base> newBase( 
    factory.create( 
      className,
      infile ) );
  return newBase;
}
"The restoreBaseFromStream function reads the class identifier from the stream and just passes that on to the factory's create function, along with the input stream. I'll also have to write specializations for PersistentStorage< string, istream > and PersistentStorage< string, ostream >. [1] The interesting part is in the specialization on PersistentStorage< Base::BaseParams, Storage >." She added the restore function to the specialization:
  Base::BaseParams & restore()
  {
    param_.value_ = 
      PersistentStorage< 
        int, 
        Storage>( warehouse_ ).
        restore();
    return param_;
  }
"Like the save() function, this function will take advantage of an int/ostream specialization, and pass on the work to it."

"Well done " the Guru said. "Now, we have to turn our attention to the derived classes. For the derived class, we need to do four things: register the class in the factory, create the DerivedParams struct, override the retrieveStorageInfo function, and provide a PersistentStorage partial specialization. Registering the class is simple enough:"

RegisterInFactoryWithParams
<
  Base, 
  Base::BaseParams,
  Derived, 
  Derived::DerivedParams, 
  istream, 
  ostream
> registerWithParams( 
    Derived::derivedClassID() );
"Well, the DerivedParams will be pretty straightforward," I said, as I took the keyboard from Wendy and started typing:

struct DerivedParams : 
  public Base::BaseParams
{
  std::string word_;
};
I smiled as I slid the keyboard back to Wendy. Then I caught the Guru's expression, and my heart sank. I had seen that expression many times before, when I was sure I had the answer but had overlooked something.

"Perhaps you should override the retrieveStorageInfo function," she simply said.

"Uh, OK," I replied.

auto_ptr< Base::BaseParams >
Derived::retrieveStorageInfo() const
{
    auto_ptr< BaseParams > bp(
      Base::retrieveStorageInfo()
    );
"Oh, I get it now," I said. "The DerivedParams struct has to be initialized from the BaseParams struct that we get from Base::retrieveStorageInfo(), so we need to add an overloaded constructor to DerivedParams." I went back, and added the requisite constructor to DerivedParams:
  DerivedParams(
    const BaseParams & bp,
    const string & word )
  : BaseParams( bp ),
    word_( word )
  {
  }
I smiled at the Guru. My smile faded, though, because she still had that "you're forgetting something" look on her face.

"Perhaps you should implement the PersistentStorage::restore function now," she said. I turned to the keyboard again:

template< typename Storage >
class PersistentStorage
<
  Derived::DerivedParams,
  Storage 
>
: public PersistentStorage
  <
    Base::BaseParams,
    Storage
  >
{
  typedef PersistentStorage
  <
    Base::BaseParams,
    Storage 
  > Parent;
public:
  Derived::DerivedParams & restore()
  {
    Derived::DerivedParams param(
      Parent::restore(),
      PersistentStorage<
        std::string,
        Storage>( warehouse_ ).
        restore()
      );
    return param;
  }
"Ah, of course," I said as soon as I finished typing. "The restore() function returns a reference, so it can't return a local object. The object it returns will have to have a longer lifetime, so I'll have to... um..." I faltered, as I thought about how to fix the problem.

"...make the param object a member of the PersistentStorage template," the Guru picked up smoothly. "Since in this case, the member is initialized when the PersistentStorage template is initialized, it cannot use overloaded constructors to set up the member variables, you will have to provide an assignment operator." She took over the keyboard, and added the assignment operator.

DerivedParams & operator=(
  const BaseParams & bp )
{
  BaseParams::operator=(bp);
  return *this;
}
She paused before erasing the constructor. "There are, of course, certain implementations that will require you to construct the DerivedParams object each time you call restore," she said. "When you are restoring objects that contain references to other objects, the DerivedParameters cannot, of course, reseat the reference to refer to the new object, so it must be reconstructed. Handling references in persistence, though, is a much wider topic, and deserves a conversation on its own."

"Now, though, you are ready to finish off the retrieveStorageInfo function." She slid the keyboard back to me.

auto_ptr< Base::BaseParams >
Derived::retrieveStorageInfo() const
{
  auto_ptr< BaseParams > bp(
    Base::retrieveStorageInfo()
  );
  auto_ptr< DerivedParams >
    ret( new DerivedParams );
  *ret = *bp;
  ret->word_ = word_;
  return auto_ptr< BaseParams >
    ( ret.release() );
}
"Ick," I said, "I'm dynamically allocating two objects, one of which is only used to initialize the final object being returned."

"Well, you could make it a two-stage creation," Wendy commented. "Have one virtual function that returns a new, default-initialized parameter object, and another function that initializes it." She wrote on my whiteboard:

auto_ptr< BaseParams >
  createStorageInfo() const;
void setStorageValues(
  BaseParams & ) const;
"The drawback here, of course, is having two virtual functions to override, instead of one."

"Let us stick with the original approach for now," the Guru interrupted, "as time grows short. Now you will want to rework your PersistentStorage template's restore function."

Derived::DerivedParams & restore()
{
  param_ = Parent::restore();
  param_.word_ = PersistentStorage
  <
    std::string,
    Storage
  >( warehouse_ ). restore();
  return param_;
}
"It's simple enough," I explained. "I use the assignment operator to reinitialize the BaseParams portion, then set the DerivedParams portion –– the word_ member –– and return the member."

"All that remains now is to finish the implementation of the PersistentStorage template specialization, for the save() function." Wendy took over at the keyboard:

  void save( const Derived::DerivedParams & params )
  {
    Parent::save( params );
    PersistentStorage
    <
      std::string,
      Storage
    >( warehouse_ ).save(
      params.word_ );
  }
"This one really is straightforward," she explained. "All we do is invoke the parent specialization of PersistentStorage, then store our own data."

"Well, done, my children," the Guru slipped back into her act, as Bob returned from the lunch room with his latte. "My apprentice, what are the benefits of this approach?"

"Well, for one, the class hierarchy is isolated from its storage medium. We wrote specializations to use stream storage, but if we wanted to use, say, a database, then all we have to do is provide a new set of specializations. These specializations can be in completely independent source files.

"For another, we're not limited in the class constructors. Each class in the hierarchy can take its own number and type of parameters."

"Wendy, the drawbacks?"

"It still suffers from the static initialization order problem. You can't be sure a particular class will be registered before main executes.

"You have to have access to the source files––you couldn't use this on a third party class hierarchy. Not as-is, at least; you might be able to provide a wrapper class hierarchy for persistence."

"Well done, my children," the Guru said as she got up and glided away.

Notes

[1] Space does not permit showing the specializations for int and stream. The source code for this article, available at <www.cuj.com/code>, contains a complete working program, that includes these specializations.

About the Author

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.