Odds and ends: disposable objects
Welcome back.
As promised, more content from the newly completed Implementing QuantLib. This week, a class with its feet firmly planted in the past.
In other news, there are still places available for my next course. Click the link, I’ll wait.
Done? Great. Here we go.
Subscribe to my Substack to receive my posts in your inbox, or follow me on Twitter or LinkedIn if you want to be notified of new posts, or subscribe via RSS if you’re the tech type: the buttons for all that are in the footer. Also, I’m available for training, both online and (when possible) on-site: visit my Training page for more information.
Disposable objects
The Disposable
class template was an attempt to implement move
semantics in C++98 code. To give credit where it’s due, we took the
idea and technique from an article by Andrei Alexandrescu [1] in which
he described how to avoid copies when returning temporaries.
The basic idea is the one that was starting to float around in those
years and that was given its final form in C++11: when passing a
temporary object, copying it into another one is often less efficient
than swapping its contents with those of the target. You want to move
a temporary vector? Copy into the new object the pointer to its
storage, instead of allocating a new one and copying the elements. In
modern C++, the language itself supports move semantics with the
concept of rvalue reference [2]; the compiler knows when it’s dealing
with a temporary, and we can use std::move
in the few cases when we
want to turn an object into one. In our implementation, shown in the
following listing, we don’t have such support; you’ll see the
consequences of this in a minute.
The class itself is not much to look at. It relies on the template
argument implementing a swap
method; this is where any resource
contained inside the class are swapped (hopefully in a cheap way)
instead of copied. The constructors and the assignment operator all
use this to move stuff around without copies—with a difference,
depending on what is passed. When building a Disposable
from
another one, we take it by const
reference because we want the
argument to bind to temporaries; that’s what most disposables will be.
This forces us to use a const_cast
in the body, when it’s time
to call swap
and take the resources from the disposable. When
building a Disposable
from a non-disposable object, instead, we
take is as a non-const
reference; this is to prevent ourselves
from triggering unwanted destructive conversions and from finding
ourselves with the empty husk of an object when we thought to have a
usable one. This, however, has a disadvantage; I’ll get to it in a
minute.
The next listing shows how to retrofit Disposable
to a
class; Array
, in this case.
As you see, we need to add a
constructor and an assignment operator taking a Disposable
(in
C++11, they would be a move constructor and a move assignment
operators taking an rvalue reference) as well as the swap
method that will be used in all of them. Again, the constructors take
the Disposable
by const
reference and cast it later, in
order to bind to temporaries—although now that I think of it, they
could take it by copy, adding another cheap swap.
Finally, the way Disposable
is used is by returning it from
function, like in the following code:
Returning the array causes it to be converted to Disposable
,
and assigning the returned object causes its contents to be swapped
into a
.
Now, you might remember that I talked about a disadvantage when I
showed you the Disposable
constructor being safe and taking an
object by non-const
reference. It’s that it can’t bind to
temporaries; therefore, the function above can’t be written more
simply as:
because that wouldn’t compile. This forces us to take the more
verbose route and give the array a name. (Well, it doesn’t actually
force us, but writing return Disposable<Array>(Array(n, 10))
is
even uglier than the alternative.)
Nowadays, of course, we’d use rvalue references and move constructors
and forget all about the above. To tell the truth, I’ve a nagging
suspicion that Disposable
might be getting in the way of the
compiler and doing more harm than good. Do you know the best way to
write code like the above and avoid abstraction penalty in modern
C++? It’s this one:
In C++17, the copies that might have been done when returning the
array and when assigning it are guaranteed to be elided (that is, the
compiler will generate code that builds the returned array directly
inside the one we’re assigning); most recent compilers have been doing
that for a while, without waiting for the standard to bind them. It’s
called RVO, for Return Value Optimization, and using
Disposable
prevents it and thus might make the code slower
instead of faster.
Bibliography
[1] A. Alexandrescu, Move Constructors. In C/C++ Users Journal, February 2003.
[2] H.E. Hinnant, B. Stroustrup and B. Kozicki, A Brief Introduction to Rvalue References. C++ Standards Committee Paper N2027, 2006.