Fabulous Adventures In Coding
Eric Lippert is a principal developer on the C# compiler team. Learn more about Eric.
law of leaky abstractions rears its ugly head once more. I
try to never use smart pointers because... I'm not smart enough.
programmers are of course intimately familiar with AddRef and Release. The
designers of COM decided to use reference counting as the mechanism for implementing
storage management in COM, and decided to put the burden of implementing and calling
these methods upon the users.
it is very natural when using a language like C++ to say that perhaps we can encapsulate
these semantics into an object, and let the C++ compiler worry about calling the AddRefs
in the appropriate constructors, copy constructors and destructors. It
is very natural and very tempting, and I avoid template libraries that do so like
usually works fine until there is some weird, unforeseen interaction between the various
parts. Let me give you an example:
you have the following situation: you have a C++ template library map mapping IFoo pointers
onto IBar pointers,
where the pointers to the IFoo and IBar are
in smart pointers. You want the map to
take ownership of the pointers. Does
this code look correct?
sure looks correct, doesn't it?
again. Is there a memory leak there?
found code like this in a library I was maintaining once, and since I had never used
smart pointer templates before, I decided to look at what exactly this was doing at
the assembly level. A glance at the generated assembly shows that the order of operations
call srpFoo.Disown() to
get the IFoo*
call srpBar.Disown() to
get the IBar*
call map's operator passing
in the IFoo* ,
returning an IBar**
do the assignment of the IBar* to
the address returned in (3).
where is the leak? This library had C++ exception handling for out-of-memory exceptions
turned on. If the operator throws
an out-of-memory exception then the not-smart IFoo* and IBar* presently
on the argument stack are both going to leak.
correct code is to copy the pointers before you disown them:
the day that I found this I had never been in a situation before where I had
to think about the C++ order of operations for assigning and subscripting in order
to get the error handling right! The
fact that you have to know these picky details about C++ operator semantics in order
to get the error handling right is an indication that people are going to get it wrong.
me give you another example. One day
I was adding a feature to this same library, and I noticed that I had caused a memory
leak. Clearly I must have forgotten to
call Release somewhere,
or perhaps some smart pointer code was screwed up. I
figured I'd just put a breakpoint on the AddRef and Release for
the object, and figure out who was calling the extra AddRef.
-- and I am not making this up! -- is the call stack at the point of the first AddRef:
maybe you ATL programmers out there are smarter than me. In
fact, I am almost certain you are, because I have not the faintest idea what the differences
between a CComContainedObject,
a CComObjectRootBase and
a CComPolyObject are!
It took me hours to debug this memory
leak. So much for smart pointers saving
am too dumb to understand that stuff, so when I write COM code, my implementation
of AddRef is one
line long, not hundreds of lines of dense macro-ridden, templatized cruft winding
its way through half a dozen wrapper classes.
I can not beleive I found somebody that thinks like I do about the smart pointers. I agree with the author 100% and I spend 10 hours a day looking at C++ code. How do I convince myself to use this &%^@#:
template <class> class OwnershipPolicy = RefCounted,
class ConversionPolicy = DisallowConversion,
template <class> class CheckingPolicy = AssertCheck,
template <class> class StoragePolicy = DefaultSPStorage
What does "smart" stand for in a "smart pointer" when you have to call a Disown() function for it to get ownership semantics right? A good smart pointer would do ownership transfer at assignment itself, when the possibly failing operations are sorted out.
Yes, you will have to think about the stuff at the low level (this is C++, after all), but only when you are _implementing_ a smart pointer. If you need to think about it when you are _using_ it, it#s the wrong one.