QuEP 10: Separation of concerns in RelinkableHandle

Luigi Ballabio

Abstract

The RelinkableHandle class reimplements a number of functionalities which are already present in the Handle and Observable classes.

A reimplementation of the former is presented which takes advantage of code reuse while achieving separation of concerns.

Current implementation

The purpose of the RelinkableHandle class is to provide a shared point of access to a Handle. Should the latter be replaced, all the clients of the RelinkableHandle would be aware of the change and would start accessing the new Handle. Its current implementation can be sketched as follows:

template <class Type>
class RelinkableHandle {
  public:
    // constructor, copy constructor, assignment operator, destructor
    ...
    // relinking functionality
    void linkTo(const Handle<Type>& h);
    // handle functionality
    Type* operator->() const;
    bool isNull() const;
    // observable functionality - note that it does not inherit from Observable
    void registerObserver(Observer*);
    void unregisterObserver(Observer*);
  private:
    Handle<Type>** ptr_;
    int* n_;
    class InnerObserver : public Observable,
                          public Observer {
      public:
        // Observer interface
        void update() { notifyObservers(); }
    };
    InnerObserver* observer_;
};

A number of issues are present in this design, namely:

Also, all these issues force RelinkableHandle to implement the appropriate copy semantics and to perform the appropriate clean-up upon destruction.

Disadvantages:

Proposed implementation

The Handle implementation of reference-counted shared-body semantics can be reused by adding another layer of indirection between RelinkableHandle and its underlying shared instance. Namely, a class would be introduced which encapsulates the concept of shared and relinkable access to a Handle as follows:

template <class T>
class Link : public Observable, public Observer {
  public:
    Link(const Handle<T>& h = Handle<T>())
    : link_(h) {
        if (!link_.isNull())
            registerWith(link_);
    }
    // the default copy semantics and destructor are fine
    // as soon as Observer's copy semantics is fixed as in QuEP 8
    void linkTo(const Handle<T>& h) {
        unregisterWith(link_);
        link_ = h;
        if (!link_.isNull())
            registerWith(link_);
        notifyObservers();
    }
    const Handle<T>& currentLink() const { return link_; }
    void update() { notifyObservers(); }
  private:
    Handle<T> link_;
};

This would allow RelinkableHandle to be defined as:

template <class T>
class RelinkableHandle : public Handle<Link<T> > { ... };

Inheriting from Handle<Link> already solves our problems. Namely, RelinkableHandle now inherits the correct copy semantics and clean-up. Also, it can be automatically and meaningfully converted to a Handle<Observer> and transparently stored into Observer without modifying the latter. Finally, it can be made to work with another shared pointer implementation simply by replacing Handle with the latter in Link and in the inheritance declaration.

The reason why we actually inherit from Handle<Link> instead of using a simple typedef is that inheritance allows us to made the new RelinkableHandle backward-compatible and define a few syntactic shortcuts. The new class simply acts as an adapter to its base class and can be sketched as follows:

template <class T>
class RelinkableHandle : public Handle<Link<T> > {
  public:
    RelinkableHandle(const Handle<T>& h = Handle<T>())
    : Handle<Link<T> >(new Link(h)) {}
    // relinking functionality forwarded to Link
    void linkTo(const Handle<Type>& h) { (**this).linkTo(h); }
    // operator-> is forwarded to the Handle<T> rather than to Link
    Handle<T>& operator->() const { return (**this).currentLink(); }
    // isNull checks the Handle<T> rather than itself
    bool isNull() const { return (**this).currentLink().isNull(); }
};

Conclusion

An implementation of RelinkableHandle was proposed which leverages functionalities already implemented in the QuantLib library. The resulting design is simpler and more easily adaptable to future changes.

Feedback

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