1

[One answer, below is to force a choice between .release() and .get(), sadly the wrong general advice is to just use .get()]


Summary: This question is asking for technical reasons, or behavioural reasons (perhaps based on experience), why smart pointers such as unique_ptr are stripped of the major characteristic of a pointer, i.e. the ability to be passed where a pointer is expected (C API's).

I have researched the topic and cite two major claimed reasons below, but these hardly seem valid.

But my arrogance is not boundless, and my experience not so extensive as to convince me that I must be right.

It may be that unique_ptr was not designed simple lifetime management of dumb C API pointers as a main use, certainly this proposed development of unique_ptr would not be [http://bartoszmilewski.com/2009/05/21/unique_ptr-how-unique-is-it/], however unique_ptr claims to be "what auto_ptr should have been" (but that we couldn't write in C++98)" [http://www.stroustrup.com/C++11FAQ.html#std-unique_ptr] but perhaps that is not a prime use of auto_ptr either.


I'm using unique_ptr for management of some C API resources, and shocked [yes, shocked :-)] to find that so-called smart pointers hardly behave as pointers at all.

The API's I use expect pointers, and I really don't want to be adding .get() all over the place. It all makes the smart pointer unique_ptr seem quite dumb.

What's the current reasoning for unique_ptr not automatically converting to the pointer it holds when it is being cast to the type of that pointer?

void do_something(BLOB* b);
unique_ptr<BLOB> b(new_BLOB(20));

do_something(b); 
// equivalent to do_something(b.get()) because of implicit cast

I have read http://herbsutter.com/2012/06/21/reader-qa-why-dont-modern-smart-pointers-implicitly-convert-to/ and it remarkably (given the author) doesn't actually answer the question convincingly, so I wonder if there are more real examples or technical/behavioural reasons to justify this.

To reference the article examples, I'm not trying do_something(b + 42), and + is not defined on the object I'm pointing to so *b + 42 doesn't make sense.

But if it did, and if I meant it, then I would actually type *b + 42, and if I wanted to add 42 to the pointer I would type b + 42 because I'm expecting my smart pointer to actually act like a pointer.

Can a reason for making smart pointers dumb, really be the fear that the C++ coder won't understand how to use a pointer, or will keep forgetting to deference it? That if I make an error with a smart pointer, it will silently compile and behave just as it does with a dumb pointer? [Surely that argument has no end, I might forget the > in ->]

For the other point in the post, I'm no more likely to write delete b than I am to write delete b.get(), though this seems to be a commonly proposed reason (perhaps because of legacy code conversions), and discussed C++ "smart pointer" template that auto-converts to bare pointer but can't be explicitly deleted however Meyers ambiguity of 1996, mentioned in http://www.informit.com/articles/article.aspx?p=31529&seqNum=7 seems to answer that case well by defining a cast for void* as well as for T* so that delete can't work out which cast to use.

The delete problem seems to have had some legitimacy, as it is likely to be a real problem when porting some legacy code but it seems to be well addressed even prior to Meyer (http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/sp_techniques.html#preventing_delete)

Are there more technical for denying this basic pointer-behaviour of smart pointers? Or did the reasons just seem very compelling at the time?

Previous discussions contain general warnings of bad things [Add implicit conversion from unique_ptr<T> to T* , Why doesn't `unique_ptr<QByteArray>` degrade to `QByteArray*`?, C++ "smart pointer" template that auto-converts to bare pointer but can't be explicitly deleted, http://bartoszmilewski.com/2009/05/21/unique_ptr-how-unique-is-it/] but nothing specific, or that is any worse than use of non-smart C pointers to C API's.

The inconvenience measured against the implied risks of coders blindly adding .get() everywhere and getting all the same harms they were supposed to have been protected against make the whole limitation seem very unworthwhile.


In my case, I used Meyer's trick of two casts, and take the accompanying hidden risks, hoping that readers will help me know what they are.

template<typename T, typename D, D F>
class unique_dptr : public std::unique_ptr<T, D> {
    public: unique_dptr(T* t) : std::unique_ptr<T, D>(t, F) { };
    operator T*() { return this->get(); }
    operator void*() { return this->get(); }
};

#define d_type(__f__) decltype(&__f__), __f__

with thanks to @Yakk for the macro tip (Clean implementation of function template taking function pointer, How to fix error refactoring decltype inside template)

using RIP_ptr = unique_dptr<RIP, d_type(::RIP_free)>;
RIP_ptr rip1(RIP_new_bitmap("/tmp/test.png"));

No that's a smart pointer I can use. * I declare the free function once when the smart pointer type is defined * I can pass it around like a pointer * It gets freed when the scope dies

Yes, I can use the API wrongly and leak owned references, but .get() doesn't stop that, despite it's inconvenience.

Maybe I should have some consts in there as a nod to lack of ownership-transference.

Community
  • 1
  • 1
Sam Liddicott
  • 1,265
  • 12
  • 24
  • 1
    It think it's fair to say that there are broadly 2 approaches to writing software - ease and safety. It seems that ease always comes at the expense of safety and vice-versa. The c++ committee push c++ strongly in the direction of safety and determinism. Presumably they take the view that for easy expression there are looser languages available. – Richard Hodges Nov 02 '15 at 10:54
  • I think you are right there. I'm looking for information on the view that having to add `.get()` is somehow *safer* – Sam Liddicott Nov 02 '15 at 10:59
  • 1
    by 'safer' what they mean is that you have to *deliberately* do this dangerous thing. You can't do it by accident through an implicit cast (which an inexperienced or tired developer may not notice). Getting hold of the pointer is, as you know, holding the grenade with the pin taken out. The standards guys feel that this is something people should know they're doing :-) – Richard Hodges Nov 02 '15 at 11:11
  • I don't find that some people will habitually go out of their way to endanger themselves a convincing argument that we shouldn't have safety features. –  Nov 02 '15 at 11:41
  • 1
    @Hurkyl the question is one of safety the feature brings, given lack on information or reasonable argument, even by Herb Sutter. Simply requiring the user to add `.get()` everywhere is not of itself a safety feature. As I since learned, forcing a choice between `.get()` and `.release()` is an opportunity for the coder to add safety, **iff** they understand that this is the choice being forced, but research indicates that this is not commonly understood, and `.get()` is indeed blindly used and recommended as the **safer** option. It isn't any safer; but the choice is an opportunity for safety. – Sam Liddicott Nov 02 '15 at 11:55
  • @Sam: That doesn't rebut what I said in any way. I don't dispute people who don't understand will circumvent the safety feature. I'm saying that in of itself is not a reason to take away the feature from people who do understand. (also, you seem to be overlooking that functions can take and return smart pointers rather than raw pointers) –  Nov 02 '15 at 14:43
  • @Hurkyl I don't intend to rebut what you say; I agree that people can overcome safety features, especially when the safety is unfortunately a secret - but I was commenting on the application of what you say. The whole question is on what **benefit** there is in (as you say) *[taking] away a feature from people who do understand*. I came across many who find `.get()` to be a *safety feature* in a superstitious sense but with no idea of what safety it gave or how it gave it. On your other point, I think that functions that take and return smart pointers won't be subject to automatic conversion – Sam Liddicott Nov 02 '15 at 15:17

1 Answers1

3

One answer that I'm surprised I haven't found in my searches is implied in the documentation for unique_ptr::release [http://en.cppreference.com/w/cpp/memory/unique_ptr/release]

release() returns the pointer and the unique_ptr then references nullptr and so clearly this can be used for passing on the owned reference to an API that doesn't use smart pointers.

By inference, get() is a corresponding function to pass unowned reference

As a pair, these functions explain why automatic de-referencing is not permitted; the coder is forced to replace each pointer use either with .release() or .get() depending on how the called function will treat the pointer.

And thus the coder is forced to take intelligent action and choose one behaviour or the other, and upgrade the legacy code to be more explicit and safe.

.get() is a weird name for that use, but this explanation makes good sense to me.

Sadly, this strategy is ineffective if the only advice the coder has is to use .get(); the coder is not aware of the choice and misses a chance to make his code safe and clear.

Sam Liddicott
  • 1,265
  • 12
  • 24