"Do we know what it is?" I gestured at the device lying on the cold metal table before us. It had been one of the first to have been retrieved by the recon parties exploring the immense building buried below us in the ice.
Jeannine shook her head. "Nyet, tovarisch. It could be a toaster, a hyperdrive motor, a child's toy, or just junk. Last week we thought we had the power system worked out, for this and the other devices, but when we ran the juice through them nothing happened. The techs are back to the drawing board as far as the power goes, but they're hopeful that they'll get it right soon."
"And what makes them think they will?"
"Hope."
I glanced at the clock on the wall of the chamber. "Well, it's nearly time. I'm beat, and I'm freezing. Every time we ask the recon teams to give us something new to work on, they produce something new and different. I just wish we could understand even one of these."
"I wish we knew more about what was coming out of our little historical 'artifact factory' here," Jeannine agreed.
"That reminds me of something that happened to me back when"
Jeannine rolled her eyes. "Tell me when we get someplace warm."
"Bad news," I called glumly over the cubicle wall to Wendy.
"Wazzup?" she gophered up.
"You know that class I worked on last week?"
"Not intimately, but do go on."
I ignored that. "Well," I continued, "I have to create a class factory for it. The Guru suggested I look at the class factory the client group wrote."
"Yeah, so?" Then a sudden look of dismay crossed her face: "Oh. Bob wrote that, didn't he?"
I nodded glumly. "Well, one thing I'll give him credit for every time I crack open his code, I learn how not to do something." Wendy giggled and sank back into her chair.
I sighed and checked out the source code. It wasn't quite as bad as I had anticipated. There was only one humongous series of cascading if statements I had expected a lot worse. Still, it was pretty hairy code, so with the Guru's blessing I set about implementing an Abstract Class Factory [1]. Absent any requirements for multithreading or other concurrency, I decided to implement the factory as a singleton:
class BaseFactory { typedef std::auto_ptr<Base> (*BaseCreateFn)(); typedef std::map<std::string, BaseCreateFn> FnRegistry; FnRegistry registry; BaseFactory() {} BaseFactory(const BaseFactory &); // Not implemented BaseFactory & operator=(const BaseFactory &); // Not implemented public: static BaseFactory & instance() { static BaseFactory bf; return bf; } bool RegCreateFn(const std::string &, BaseCreateFn); std::auto_ptr<Base> Create(const std::string &) const; }; bool BaseFactory::RegCreateFn(const std::string & className, BaseCreateFn fn) { registry[className] = fn; return true; } std::auto_ptr<Base> BaseFactory::Create(const std::string &className) const { std::auto_ptr<Base> theObject(0); FnRegistry::const_iterator regEntry = registry.find(className); if (regEntry != registry.end()) { theObject = regEntry->second(); } return theObject; }
In the Base implementation file, I added:
namespace { std::auto_ptr<Base> CreateBase() { return std::auto_ptr<Base>(new Base); } bool dummy = BaseFactory::instance().RegCreateFn("Base", CreateBase); }
"OK, that's cool," I thought. "You register a function with the factory, and creating an instance is as simple as one, two, three:"
int main() { std::auto_ptr<Base> anObject = BaseFactory::instance().Create("Base"); }
I then proceeded to create a derived class, to test creating it via the factory. In the derived class's implementation file, I added:
namespace { std::auto_ptr<Derived> CreateDerived() { return std::auto_ptr<Derived>(new Derived); } bool dummy = BaseFactory::instance().RegCreateFn("Derived", CreateDerived); }
But the compiler stopped me cold it complained that it couldn't convert CreateDerived to the correct type. After puzzling over it for a moment, I remembered the problem I had experienced previously with implicit conversions to pointers-to-Base [2]. I realized I had just run into another situation where the compiler couldn't implicitly convert the pointers, so I had to rewrite the create function slightly:
namespace { std::auto_ptr<Base> CreateDerived() { return std::auto_ptr<Base>(new Derived); } bool dummy = BaseFactory::instance().RegCreateFn("Derived", CreateDerived); }
Looking back at the base class, I realized that the registration code was almost identical, so I created a macro to wrap it up:
#define REGISTER_CLASS(BASE_CLASS, DERIVED_CLASS) \ namespace \ { \ std::auto_ptr<BASE_CLASS> Create##DERIVED_CLASS() \ { \ return std::auto_ptr<BASE_CLASS>(new DERIVED_CLASS); \ } \ bool dummy=BaseFactory::instance().RegCreateFn( \ #DERIVED_CLASS, Create##DERIVED_CLASS); \ }
Using the macro was quite simple:
REGISTER_CLASS(Base, Base)
"Not bad for a few hours' work," I muttered when I was done.
"Yes, indeed, my child. But you can do better."
I jumped yet again at the Guru's soft voice behind me. "Wha?"
"Macro expansions like that," she explained, "are difficult to read and understand, and macros are not type safe. Also, what is the purpose of the bool dummy variable?"
"Well," I said, slightly defensively, "it was the only way I could think of to ensure that the registration function got called automatically. I wanted to get away from the typical factory code that requires a modification each time you add a new class."
She inclined her head. "Wise thinking, my child. However, your factory is not very generic. We are dealing with more and more classes that require abstract factories. I want you to create a factory that will handle, not require, modification for each class hierarchy it will be used on."
"A generic abstract factory? That's a pretty tall order, isn't it?"
"My young apprentice, you are not afraid of a challenge, are you? 'Experience is by industry achieved...' "
" '... and perfected by the swift course of time,' " I finished [3]. "No, I'm not afraid of the challenge, but I don't want the swift course of time to turn the schedule aside."
"You are most of the way there already. All you need to do is think generically," she observed.
"Think generically... as in, templates!" I exclaimed and turned back to my monitor. A few minutes later, I glanced behind me and saw that the Guru had disappeared again as silently as she had appeared. I shivered, but only slightly, and went back to work.
After a short while, I came up with a factory template:
template <class ManufacturedType, typename ClassIDKey=std::string> class GenericFactory { typedef std::auto_ptr<ManufacturedType> (*BaseCreateFn)(); typedef std::map<ClassIDKey, BaseCreateFn> FnRegistry; FnRegistry registry; GenericFactory(); GenericFactory(const GenericFactory&); // Not implemented GenericFactory &operator=(const GenericFactory&); // Not implemented public: static GenericFactory &instance(); void RegCreateFn(const ClassIDKey &, BaseCreateFn); std::auto_ptr<ManufacturedType> Create(const ClassIDKey &className) const; };
I realized that not all classes would necessarily need a std::string as the key, so I made the key type a parameter to the template. The implementation of each function was the same as I had used for BaseFactory, except for the registration function. I came up with a more elegant template for that:
template <class AncestorType, class ManufacturedType, typename ClassIDKey=std::string> class RegisterInFactory { public: static std::auto_ptr<AncestorType> CreateInstance() { return std::auto_ptr<AncestorType>(new ManufacturedType); } RegisterInFactory(const ClassIDKey &id) { GenericFactory<AncestorType>::instance().RegCreateFn(id, CreateInstance); } };
Now, each class derived from the base class simply had to add one line to get a type-safe registration of the creation function:
RegisterInFactory<Base, Base> registerMe("Base");
The RegisterInFactory template's constructor registers the name of the class in the creation registry [4].
I glanced behind me, just in time to see the Guru approaching. I smiled to myself; for once I was slightly ahead of her game. "Very good, my apprentice," she said, coming up behind me. "Your factory is generic, portable, and does not rely on registration tables, DLL sentries, or other cumbersome techniques."
"There is one drawback, though," I interrupted. "Because the registration relies on a static object being initialized, there is no guarantee that all the creation functions will be registered if you use the factory before main begins execution."
"Exactly, my apprentice, your factory is subject to the Static Initialization Order Fiasco, as explained by the prophet Cline [5]." She turned to leave.
"Wait a second," I called. The Guru turned back. I was trying to figure out a graceful way of asking the question that was on my mind. The Guru patiently tucked a lock of her hair behind her ear while she waited. "About Bob. If he's such a bad programmer," I managed to say, "how come..."
"Why is he still here?" the Guru supplied. I nodded. The Guru looked down in thought. "Have you noticed that Bob hangs out with the execs? Senior management thinks he's some sort of golden boy and can do no wrong. Because of our... um... past, Bob has them convinced that any of my complaints about his job performance are just the petty grievances of a bitter ex-wife. And, according to him, anyone who agrees with me has been drawn into my little plots against him his words, not mine."
"Man, that must be tough. Why do you put up with it? You could find a new job like that." I snapped my fingers.
She shrugged. "Other than Bob, I like it here. Besides, if I left, I wouldn't get the satisfaction of bugging the heck out of him with this act." We both laughed. "Seriously, though," she continued, "this company has a lot of opportunities for career growth. For example, our new biomedical device division is going to need a head of software development I've applied for the position."
"Cool! Good luck," I said. The Guru nodded her thanks. I turned back to my workstation, to put the finishing touches on the abstract factory template.
It was late at night a few days later when the news came, but I didn't find out about it until I got up the next morning. Still bleary-eyed, I was walking into the mess when the unusual level of noise made me put my hands over my ears and wince. "What's with all the chatter?" I muttered grumpily at the dozen people who were already there, talking excitedly. "It's too early for you guys to be this perky."
It was Major Gilb who answered, smiling. "We think we've got it, my boy, we think we do."
"What?" I grumped through mental cobwebs.
"Looks like the power's on," Jeannine said simply.
Notes and References
[1] Gamma, Helm, Johnson, Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software (Addison Wesley, 1995).
[2] Jim Hyslop and Herb Sutter. "Conversations: Roots," C/C++ Users Journal C++ Experts Forum, May 2001, http://www.cuj.com/experts/1905/hyslop.htm.
[3] William Shakespeare. Two Gentlemen of Verona, I iii 22.
[4] The complete factory file, along with a small driver program, is available on the CUJ website at hyslop.zip.
[5] Marshall Cline. C++ FAQ-Lite, http://www.parashift.com/c++-faq-lite/.
Jim Hyslop is a senior software designer at Leitch Technology International Inc. He can be reached at [email protected].
Herb Sutter is an independent consultant and secretary of the ISO/ANSI C++ standards committee. He can be reached at [email protected].