Reader Q&A: Why don’t modern smart pointers implicitly convert to *?

Today a reader asked a common question:

Why doesn’t unique_ptr (and the ilk) appear to have an operator overload somewhat as follows:

operator T*() { return get(); };

The reason I ask is because we have reams of old code wanting raw pointers (as function parms), and I would like to replace the outer layers of the code which deal with the allocation and deallocation with unique_ptrs without having to either ripple unique_ptrs through the entire system or explicitly call .get() every time the unique_ptr is a parm to a function which wants a raw pointer.

What my programmers are doing is creating a unique_ptr and immediately using get() to put it into a local raw pointer which is used from then on. Somehow that doesn’t feel right, but I don’t know what would be the best alternative.

In the olden days, smart pointers often did provide the convenience of implicit conversion to *. It was by using those smart pointers that we learned it caused more problems than it solves, and that requiring people to write .get() was actually not a big deal.

For an example of the problems of implicit conversions, consider:

unique_ptr p( new widget );
use( p + 42 ); // error (maybe he meant "*p + 42"?)
    // but if implicit conversion to * were allowed, would silently compile -- urk
delete p; // error
    // but if implicit conversion to * were allowed, would silently compile -- double urk

For more, see also Andrei’s Modern C++ Design section 7.7, “Implicit Conversion to Raw Pointer Types.”

However, this really isn’t as bad as most people fear for several reasons, including but not limited to:

  • The large majority of uses of the smart pointer, such as calling member functions on the object (e.g., p->foo())  just work naturally and effortlessly because we do have operator->.
  • You rarely if ever need to say unique_ptr on a local variable, because C++11’s auto is your friend – and “rarely” becomes “never” if you use make_unique which is described here and should become standard in the future.
  • Parameters (which you mention) themselves should almost never be smart pointers, but should be normal pointers and references. So if you’re managing an object’s lifetime by smart pointer, you do write .get() – but only once at the top of each call tree. More on this in the current GotW #105 – solution coming soon, watch this space.

3 thoughts on “Reader Q&A: Why don’t modern smart pointers implicitly convert to *?

  1. My employer’s smart pointer class provides the implicit conversion. I don’t think we’ve ever had either of the problems you mention, in 15 years or so of writing code in a huge code base. If it is a problem in the routine that declares the pointer, it will also be a problem in all the functions that take a raw T * (in other words, all the places that required the get()). Since best practice still allows for using raw pointers sometimes, they can’t be that bad. For me the purpose of smart pointers is lifetime management, not disabling pointer arithmetic.

    I think a bigger reason for not providing the conversion is code like:
    shared_ptr p0( new int );
    int *p1 = p0;
    shared_ptr p2( p1 );

    where you end up with two reference counts and double-deletion. This is not a problem in my employer’s code base because our smart pointer is intrusive, so the final line uses the same count as the first line. We can freely and safely convert between smart and raw pointers. You can’t do that with shared_ptr or unique_ptr.

    So I do agree with the choice made for unique_ptr and shared_ptr, but not the reason you gave.

    (I’d add that sprinkling .get() everywhere is quite a big deal, because it’s 6 characters of clutter, which can get painful especially when several pointers are involved. When we introduced our in-house smart pointers, it was long enough ago that we needed to make them easy to use to encourage their use. So I feel for your reader needing workarounds.)

  2. I came across this bit of wisdom many years ago and I’ve never forgotten: The more a smart pointer acts like a dumb pointer, the more it _is_ a dumb pointer.

  3. The delete p; part can be stopped by having another version of implicit conversion to void*. But, I see another problem as well.

    SomeClass* Retrieve()
    unique_ptr p( … );

    return p; // oops. meant p.release();

Comments are closed.