Controlling an Object's Creation Based on Its Key Value Representing a Unique Resource

Summary

Over the years of having to write server applications, one of the recurring tasks I dealt with was the problem of creating an instance of a class associated with a unique resource of the operating system, such as a file. This article presents a creational design pattern that I call "Unique;" it's kind of a generalization of the singleton design pattern.

The goal is to provide the developer with an approach that allows you to concentrate on business logic without worrying much about how the object of the class will be created in environments such as client/server architecture, where multiple requests from a client must be processed safely and efficiently.

Introduction

Assume your class does various file processing and clients connect to the same or different files. If you take the simple approach of instance creation per client's request, you would face the problem of keeping all instances representing the same file in sync. At the same time, you would try to synchronize instances that reference different files, which might be unnecessary.

Say you are implementing a logging service where different clients might request logging messages to the same file or different files. You want to serialize requests so the resulting text file can be readable. For this case, you might decide to use mutex for this purpose. The name of the mutex you can generate is based on the full file name, which is unique. This would work. However, mutex is slow compared to a critical section on the Win32 platform. If you use a critical section to achieve better performance, this approach does not work because the critical section does not have a name. So, you decide to synchronize all requests with a critical section. Requests addressed to the same file would work faster, but at the same time two simultaneous requests addressed to different files would be synchronized as well.

What you really want is to create an instance per file. In other words, you want to control instance creation based on a key value ("divide and conquer"). In most cases, such a key would be the file name, but it also can be window handler or port number, to name a few.

Usage

Look at the design steps. At this point, I assume you've had a look at the Unique.h file. First, you develop a class that implements your business logic. In the example, it is file processing.

Listing 1.

class CMyFile
{
   .   .   .
   void SomeFunc();
};

.   .   .
CMyFile f;

The next step is to perform the following transformation (see Listing 2). You derive the existing class from the Implementation class (the base class declared in Unique.h; see Listing 3) with a template parameter that corresponds to your key's type. To simplify, assume the that string is a type that represents a file name. Next, you create the class IMyFile, which derives from the IUnique class with the first template parameter pointed to CMyFile and the second parameter defining type of the key (string). IMyFile is the interface to CMyFile. Any object of CMyFile should be replaced with IMyFile. Any public function of CMyFile should be accessed via corresponding wrapper function implemented in IMyFile using reinterpret_cast<CMyFile*>(m_pImp) object.

Listing 2.

#include "Unique.h"
// Implementation class
class CMyFile : public Implementation<string>
{
   .   .   .
   public:
      CMyFile(string filename);
      .   .   .
      void SomeFunc();
};
// Interface class
class IMyFile : public IUnique<CMyFile, string>
{
   .   .   .
   public:
      IMyFile(string filename) : IUnique<CMyFile,
                                 string>(filename) {};
   .   .   .
   // Any public function of implementation class can be
   // accessed via m_pImp
   void SomeFunc()
   {
      reinterpret_cast<CMyFile*>(m_pImp)->SomeFunc();
   }
};

.   .   .
// Replace CMyFile instances with IMyFile
IMyFile* pf;

Notice that the most significant requirement for the implementation class is that it must implement a constructor with the key as the first parameter, which means you have to know the key value (file name) at construction time. You might come up with parallel hierarchies of the class (it's better to use composition).

Notice also that deriving CMyFile from the Implementation class might be a result of multi inheritance. However, this is not a problem because the Implementation class is a trivial class (no member variables). Also, the Implementation class has a virtual destructor and the key parameter of the constructor is passed by value. For the interface, the class situation is even better. Every time you create an instance, you call the std::map find function. Every time the destructor of interface class is invoked, you use a linear search in the map. You assume that the extra housekeeping size and time of object creation/destruction is not that critical compared to class functions calls, for which IUnique provides m_pImp member.

The following is an example of an instance diagram to assist you.

Figure 1

The code of IUnique (Unique.h) maintains multiple collections for different implementation classes. It covers additional parameters (up to four) that real constructor might have. Both the implementation-derived class and the IUnique derived class could be part of hierarchies.

Passing void* as a template parameter is a result of implementating a singleton, which I call a "singleton on demand," to distinguish it from a "lifetime singleton"1—the single object of the class, once created, continues to exist until the end of the application. IUnique uses exception-safe thread synchronization for the Win32 platform implemented in InprocSync.h.

The source code also includes FileName.h and FileName.cpp that implement a class of type file name and main.cpp as a test application. You need such a class (CFileName) to reflect the fact that different strings might refer to the same file (capitalization, full/short file name, using back/forward slash, ANSII/Unicode string, UNC format, and more).

Conclusion

The solution allows developing a robust, reliable server side application faster by reducing the time spent on implementing safe object creation. The downside is having a constructor with the key as first parameter. If you want to convert an existing application and cannot afford to change its constructor to meet the above requirement and use IUnique as a class factory, you still can get some help by using the static functions AddInstance and RemoveInstance to maintain object collections.

The IUnique implementation is claimed to be thread safe and exception safe (see Reference 2 for an exception safety issue discussion). Except for AddInstance and RemoveInstance, IUnique provides a strong exception safety guarantee, assuming that EnterCriticalSection and LeaveCriticalSection Win32 APIs, upon which the SynchronizeInproc object is built, expose no-throw behavior; it always works correctly without throwing an exception.

You also provide support for a special case "singleton on demand" as a side effect of object creation and location of the object by key value from global resolution. The IUnique derivable gets a pointer to the implementation class (m_pImp) for free because it is a member of IUnique.

The code was originally developed under VC++6 environments and later compiled and tested under VC++7.1 and VC++8. Dependencies on the Win32 platform take place in InprocSync.h and in the test application (FileName.h/cpp, main.cpp).

Try this design if it works for your project.

References

  1. Andrei Alexandescu. "Modern C++ Design Generic Programming and Design Patterns Applied". C++ In-depth series, 2001.
  2. Herb Sutter. "Exceptional C++" and "More Exceptional C++". C++ In-depth series, 2000, 2002.


About the Author

Boris Bromberg

As developer I have a long career path. I used many different computer launguages, but C++ is my favorite one. For the last six years I am working as software developer for Toronto (Canada) based company Cedara Software Corporation which specializes in medical imaging software.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: December 18, 2014 The Internet of Things (IoT) incorporates physical devices into business processes using predictive analytics. While it relies heavily on existing Internet technologies, it differs by including physical devices, specialized protocols, physical analytics, and a unique partner network. To capture the real business value of IoT, the industry must move beyond customized projects to general patterns and platforms. Check out this webcast and join industry experts as …

  • On-demand Event Event Date: October 29, 2014 It's well understood how critical version control is for code. However, its importance to DevOps isn't always recognized. The 2014 DevOps Survey of Practice shows that one of the key predictors of DevOps success is putting all production environment artifacts into version control. In this webcast, Gene Kim discusses these survey findings and shares woeful tales of artifact management gone wrong! Gene also shares examples of how high-performing DevOps …

Most Popular Programming Stories

More for Developers

RSS Feeds