Odds and ends: smart pointers and handles
Welcome back.
This week, a bit of pause after the series of posts on chapter 2 of my book. The content of this post is from appendix A, and it features the Handle class. As usual, I’ll be grateful for your feedback.
Next time, I’ll start a new series of posts on content that I haven’t published before: chapter 5, covering parameterized models and calibration. My plot for making myself write more seems to be working.
In other news, QuantLib 1.3 was released last week; here’s the list of changes. About a month ago I wrote on this blog that it would take me another couple of weeks to release it. Some people never learn… If you haven’t downloaded it yet, go get it.
Follow me on Twitter if you want to be notified of new posts, or add me to your circles, or subscribe via RSS: the buttons for that are in the footer. Also, make sure to check my Training page; you might be still in time for the next course.
Odds and ends: smart pointers and handles
The use of run-time polymorphism dictates that many, if not most, objects be allocated on the heap. This raises the problem of memory management—a problem solved in other languages by built-in garbage collection, but left in C++ to the care of the developer.
I will not dwell on the many issues in memory management, especially since they are now mostly a thing of the past. The difficulty of the task (especially in the presence of exceptions) was enough to discourage manual management; therefore, ways were found to automate the process.
The weapons of choice in the C++ community came to be smart pointers:
classes that act like built-in pointers but that can take care of the
survival of the pointed objects while they are still needed and of
their destruction when this is no longer the case. Several
implementations of such classes exist which use different techniques;
we chose the smart pointers from the Boost libraries [1] (most notably
shared_ptr
, now included in the ANSI/ISO C++ standard [2]). You can
browse the Boost site for documentation; here, I’ll just mention that
their use in QuantLib completely automated memory management. Objects
are dynamically allocated all over the place; however, there is not
one single delete
statement in all the tens of thousands of lines of
which the library consists.
Pointers to pointers (if you need a quick refresher, see the aside at the bottom of the post for their purpose and semantics) were also replaced by smart equivalents. We chose not to just use smart pointers to smart pointers; on the one hand, because having to write
gets tiresome very quickly—even in Emacs; on the other hand,
because the inner shared_ptr
would have to be allocated dynamically,
which just didn’t felt right; and on the gripping hand, because it
would make it difficult to implement observability. Instead, a class
template called Handle
was provided for this purpose. Its
implementation, shown in the listing below, relies on an intermediate
inner class called Link
which stores a smart pointer. In turn, the
Handle
class stores a smart pointer to a Link
instance, decorated
with methods that make it easier to use it. The desired behavior is
obtained almost for free; since all copies of a given handle share the
same link, they are all given access to the new pointee when any one
of them is linked to a new object.
Listing: Outline of the Handle
class template.
The contained shared_ptr<Link>
also gives the handle the means to be
observed by other classes. The Link
class is both an observer and an
observable; it receives notifications from its pointee and forwards
them to its own observers, as well as sending its own notification
each time it is made to point to a different pointee. Handles take
advantage of this behavior by defining an automatic conversion to
shared_ptr<Observable>
which simply returns the contained
link. Thus, the statement
is legal and works as expected; the registered observer will receive notifications from both the link and (indirectly) the pointed object.
You might have noted that the means of relinking a handle (i.e., to
have all its copies point to a different object) were not given to the
Handle
class itself, but to a derived RelinkableHandle
class. The
rationale for this is to provide control over which handle can be used
for relinking—and especially over which handle can’t. In the
typical use case, a Handle
instance will be instantiated (say, to
store a yield curve) and passed to a number of instruments, pricing
engines, or other objects that will store a copy of the handle and use
it when needed. The point is that an object (or client code getting
hold of the handle, if the object exposes it via an inspector) must
not be allowed to relink the handle it stores, whatever the reason;
doing so would affect a number of other object (this is not as
far-fetched as it might seem; we’ve been bitten by it). The link
should only be changed from the original handle—the master
handle, if you like.
Given the frailty of human beings, we wanted this to be enforced by
the compiler. Making the linkTo
method a const
one and returning
const
handles from our inspectors wouldn’t work; client code could
simply make a copy to obtain a non-const
handle. Therefore, we
removed linkTo
from the Handle
interface and added it to a derived
class. The type system works nicely to our advantage. On the one hand,
we can instantiate the master handle as a RelinkableHandle
and pass
it to any object expecting a Handle
; automatic conversion from
derived to base class will occur, leaving the object with a sliced but
fully functional handle. On the other hand, when a copy of a Handle
instance is returned from an inspector, there’s no way to downcast it
to RelinkableHandle
.
Bibliography
[1] Boost C++ libraries, http://www.boost.org.
[2] International Standards Organization, Programming Languages — C++, International Standard ISO/IEC 14882:2011.
Aside: pointer semantics
Storing a copy of a pointer in a class instance gives the holder access to the present value of the pointee, as in the following code:
However, the stored pointer (which is a copy of the original one) is not modified when the external one is.
As usual, the solution is to add another level of
indirection. Modifying Foo
so that it stores a pointer to pointer
gives the class both possibilities.