Reputation: 1406
[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 const
s in there as a nod to lack of ownership-transference.
Upvotes: 2
Views: 266
Reputation: 1406
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.
Upvotes: 3