Reputation: 8764
I was reading Effective C++ 3rd Edition. In page 70, the author says:
Like virtually all smart pointer classes,
tr1::shared_ptr
andauto_ptr
also overload the pointer dereferencing operators (operator->
andoperator*
), and this allows implicit conversion to the underlying raw pointers (...)
He then shows an example with shared_ptr
(which was part of tr1
at the time) featuring implicit conversion based on a class named Investment
:
shared_ptr<Investment> pi1();
bool taxable1 = !(pi1->isTaxFree());
^implicit conversion
shared_ptr<Investment> pi2();
bool taxable2 = !((*pi2).isTaxFree());
^implicit conversion
Well, I have since then written a few test cases with unique_ptr
and they hold up.
I also found out about unique_ptr
supporting arrays and shared_ptr
also going to (see note). However, in my testing, implicit conversion does not seem to work for smart pointers around arrays.
Example: I wanted this to be valid...
unique_ptr<int[]> test(new int[1]);
(*test)[0] = 5;
but it is not, according to my compiler (Visual C++ 2015 Update 3).
Then, from a little research, I found some evidence suggesting that implicit conversion isn't supported at all... like this one for instance: https://herbsutter.com/2012/06/21/reader-qa-why-dont-modern-smart-pointers-implicitly-convert-to.
At this point I am in doubt. Is it supported (by the Standard), or is it not?
Note: The book might be a bit outdated on this topic, since the author also says on page 65 that "there is nothing like auto_ptr
or tr1::shared_ptr
for dinamically allocated arrays, not even in TR1".
Upvotes: 5
Views: 5278
Reputation: 4708
As StoryTeller indicates, implicit conversions would ruin the show, but I'd like to suggest another way of thinking about this:
Smart pointers like unique_ptr
and shared_ptr
try to hide the underlying raw pointer because they try to maintain a certain kind of ownership semantics over it. If you were to freely obtain that pointer and pass it around, you could easily violate those semantics. They still provide a way to access it (get
), since they couldn't stop you completely even if they wanted to (after all you can just follow the smart pointer and get the address of the pointee). But they still want to put a barrier to make sure you don't access it accidentally.
All is not lost though! You can still gain that syntactic convenience by defining a new kind of smart pointer with very weak semantics, such that it can safely be implicitly constructed from most other smart pointers. Consider:
// ipiwdostbtetci_ptr stands for :
// I promise I wont delete or store this beyond the expression that created it ptr
template<class T>
struct ipiwdostbtetci_ptr {
T * _ptr;
T & operator*() {return *_ptr;}
T * operator->(){return _ptr;}
ipiwdostbtetci_ptr(T * raw): _ptr{raw} {}
ipiwdostbtetci_ptr(const std::unique_ptr<T> & unq): _ptr{unq.get()} {}
ipiwdostbtetci_ptr(const std::shared_ptr<T> & shr): _ptr{shr.get()} {}
};
So, what's the point of this satirically named smart pointer? It's just a kind of pointer that's verbally given a contract that the user will never keep it or a copy of it alive beyond the expression that created it and the user will also never attempt to delete it. With these constraints followed by the user (without the compiler checking it), it's completely safe to implicitly convert many smart pointers as well as any raw pointer.
Now you can implement functions that expect a ipiwdostbtetci_ptr
(with the assumption that they'll honor the semantics), and conveniently call them:
void f(ipiwdostbtetci_ptr<MyClass>);
...
std::unique_ptr<MyClass> p = ...
f(p);
Upvotes: 1
Reputation: 170299
Well, here's the thing. There is no implicit conversion to the underlying pointer, you have to call a specific get
member function (it's a theme in the standard library, think std::string::c_str
).
But that's a good thing! Implicitly converting the pointer can break the guarantees of unique_ptr
. Consider the following:
std::unique_ptr<int> p1(new int);
std::unique_ptr<int> p2(p1);
In the above code, the compiler can try to pass p1
s pointee to p2
! (It won't since this call will be ambiguous anyway, but assume it wasn't). They will both call delete
on it!
But we still want to use the smart pointer as if it was a raw one. Hence all the operators are overloaded.
Now let's consider your code:
(*test)[0] = 5;
It calls unique_ptr::operator*
which produces an int&
1. Then you try use the subscript operator on it. That's your error.
If you have a std::unique_ptr<int[]>
than just use the operator[]
overload that the handle provides:
test[0] = 5;
1 As David Scarlett pointed out, it shouldn't even compile. The array version isn't supposed to have this operator.
Upvotes: 5