Reputation: 16242
I was reading Top 10 dumb mistakes to avoid with C++11 smart pointer. Number #5 reads:
Mistake # 5 : Not assigning an object(raw pointer) to a shared_ptr as soon as it is created !
int main()
{
Aircraft* myAircraft = new Aircraft("F-16");
shared_ptr<aircraft> pAircraft(myAircraft);
...
shared_ptr<aircraft> p2(myAircraft);
// will do a double delete and possibly crash
}
and the recommendation is something like:
Use
make_shared
ornew
and immediately construct the pointer with it.
Ok, no doubt about it the problem and the recommendation.
However I have a question about the design of shared_ptr
.
This is a very easy mistake to make and the whole "safe" design of shared_ptr
could be thrown away by very easy-to-detect missuses.
Now the question is, could this be easily been fixed with an alternative design of shared_ptr
in which the only constructor from raw pointer would be that from a r-value reference?
template<class T>
struct shared_ptr{
shared_ptr(T*&& t){...basically current implementation...}
shared_ptr(T* t) = delete; // this is to...
shared_ptr(T* const& t) = delete; // ... illustrate the point.
shared_ptr(T*& t) = delete;
...
};
In this way shared_ptr
could be only initialized from the result of new
or some factory function.
Is this an underexploitation of the C++ language in the library? or What is the point of having a constructor from raw pointer (l-value) reference if this is going to be most likely a misuse?
Is this a historical accident? (e.g. shared_ptr was proposed before r-value references were introduced, etc) Backwards compatibility?
(Of course one could say std::shared_ptr<type>(std::move(ptr));
that that is easier to catch and also a work around if this is really necessary.)
Am I missing something?
Upvotes: 8
Views: 2686
Reputation: 5456
I do not have any special insight into the design of shared_ptr
, but I think the most likely explanation is that the timelines involved made this impossible:
The shared_ptr
was introduced at the same time as rvalue-references, in C++11. The shared_ptr
already had a working reference implementation in boost
, so it could be expected to be added to standard libraries relatively quickly.
If the constructor for shared_ptr
had only supported construction from rvalue references, it would have been unusable until the compiler had also implemented support for rvalue references.
And at that time, compiler and standards development was much more asynchronous, so it could have taken years until all compiler had implemented support, if at all. (export templates were still fresh on peoples minds in 2011)
Additionally, I assume the standards committee would have felt uncomfortable standardizing an API that did not have a reference implementation, and could not even get one until after the standard was published.
Upvotes: 2
Reputation: 486
There's a number of cases in which you may not be able to call make_shared()
. For example, your code may not be responsible for allocating and constructing the class in question. The following paradigm (private constructors + factory functions) is used in some C++ code bases for a variety of reasons:
struct A {
private:
A();
};
A* A_factory();
In this case, if you wanted to stick the A*
you get from A_factory()
into a shared_ptr<>
, you'd have to use the constructor which takes a raw pointer instead of make_shared()
.
Off the top of my head, some other examples:
posix_memalign()
and then store it in a shared_ptr<>
with a custom deleter that calls free()
(this use case will go away soon when we add aligned allocation to the language!).mmap()
into a shared_ptr<>
with a custom deleter that calls munmap()
(this use case will go away when we get a standardized facility for shmem, something I'm hoping to work on in the next few months).shared_ptr<>
with a custom deleter.Upvotes: 1
Reputation: 11968
Pointers are very easy to copy. Even if you restrict to r-value reference you can sill easily make copies (like when you pass a pointer as a function parameter) which will invalidate the safety setup. Moreover you will run into problems in templates where you can easily have T* const
or T*&
as a type and you get type mismatches.
So you are proposing to create more restrictions without significant safety gains, which is likely why it was not in the standard to begin with.
The point of make_shared
is to atomize the construction of a shared pointer. Say you have f(shared_ptr<int>(new int(5)), throw_some_exception())
. The order of parameter invokation is not guaranteed by the standard. The compiler is allowed to create a new int, run throw_some_exception
and then construct the shared_ptr which means that you could leak the int (if throw_some_exception
actually throws an exception). make_shared
just creates the object and the shared pointer inside itself, which doesn't allow the compiler to change the order, so it becomes safe.
Upvotes: 5