QuEP 1: Polymorphic behavior by means of the pimpl idiom

Luigi Ballabio

Abstract

Composition of polymorphic types requires instances of the latter to be stored as either pointers or references since a copy operation would upcast them to their base class. However, neither pointer nor reference data members are guaranteed to point to a valid object for the entire lifetime of the enclosing object.

The solution currently implemented in QuantLib uses Handle objects which wrap a pointer ensuring that it is not deallocated as long as it is referred to by some client object. This results in adding a layer of complexity to relatively simple and ubiquitously used objects such as Calendar and DayCounter since instances of the latter must be stored, passed and returned as Handle<Calendar> and Handle<DayCounter>, respectively.

An alternative proposal is detailed here which would allow the direct use of Calendar and DayCounter instances as opposed to Handles to the same, while at the same time retaining the polymorphic behavior expected from such classes.

Current implementation

The current implementation of the Calendar class and of a client class with a Calendar data member is shown in the diagram below.

UML diagram

Calendar is an abstract class with a purely virtual method isBusinessDay(). Other methods such as isHoliday() and advance() are concrete and implemented in terms of isBusinessDay(). Concrete calendars derive from Calendar and implement the Calendar interface, namely, isBusinessDay(). Client classes contain Handles to Calendar.

Disadvantages:

Handles to Calendar must be passed, stored and returned. The Calendar class, its children, and the client class are coded as:

class Calendar {
  public:
    virtual bool isBusinessDay(const Date&) const = 0;
};

class ConcreteCalendar : public Calendar {
  public:
    bool isBusinessDay(const Date&) const;
};

class ClientClass {
  public:
    // accept a calendar upon construction
    ClientClass(const Handle<Calendar>& calendar);
    // returns its calendar
    Handle<Calendar> calendar() const;
  private:
    Handle<Calendar> calendar_;
};

which adds complexity to the declaration.

Handles must also be exported towards scripting languages for which a QuantLib module exists. This adds complexity to the SWIG interfaces, namely, Handle<Calendar> must be exported and masqueraded as a Calendar class in order to hide the smart pointer concept which has no meaning in the scripting languages. This also implies that the Calendar interface must be added to the corresponding Python class, derived classes must be exported as Handles as well, and inheritance must be faked between Handles pointing to derived Calendars.

Proposed implementation

The proposed implementation is a variation of the pimpl (Pointer IMPLementation) idiom, in which the implementation of a class is contained in a separate object contained by the class instances.

UML diagram

Calendar is now a concrete class which no virtual methods. Polymorphism is implemented by storing into a Calendar instance a Handle to a CalendarImpl object which implements the polymorphic isBusinessDay() method and to which the Calendar instance delegates the task of determining whether a given day is a business day or a holiday.

Derived Calendar classes can be defined which initialize the Handle with the desired concrete CalendarImpl. The only task of such derived Calendars is to customize the initialization process, without adding any new functionality to the Calendar interface. As such, they can be upcasted to Calendar without any loss of information occurring.

A Calendar instance can now be safely copied, since the Handle<CalendarImpl> will be copied as well, thus preserving the polymorphic behavior of the Calendar.

The Calendar class, its children, and the client class are coded as:

class Calendar {
  protected:
    // protected constructor inhibits instantiating Calendar itself
    Calendar() {}
    class CalendarImpl {
      public:
        virtual bool isBusinessDay(const Date& d) const = 0;
    };
    Handle<CalendarImpl> impl_;
  public:
    bool isBusinessDay(const Date& d) const {
        return impl_->isBusinessDay(d);
    }
};

class ConcreteCalendar : public Calendar {
  protected:
    class ConcreteCalendarImpl : public CalendarImpl {
      public:
        bool isBusinessDay(const Date& d) const;
    };
  public:
    ConcreteCalendar() {
        impl_ = Handle<CalendarImpl>(new ConcreteCalendarImpl);
    }
};

class ClientClass {
  public:
    // accept a calendar upon construction
    ClientClass(const Calendar& calendar);
    // returns its calendar
    Calendar calendar() const;
  private:
    Calendar calendar_;
};

The effect of this new implementation is that we moved complexity between layers, i.e., client classes can be defined and used in an easier way at the expense of a bit more coding for the developers of Calendar classes. We believe this to be an advantageous trade-off, since the classes which use Calendars are many more that the Calendar classes themselves.

Also, the Calendar class and its children can now be directly exported to scripting languages by means of SWIG, without resorting to the current handle masquerading.

Feedback

Feedback on the above proposal should be posted to the QuantLib-dev mailing list.