In hindsight, given make_shared
, would shared_ptr
have a constructor that takes a raw pointer had it been introduced with C++11?
Are there stro
The problem with your logic is the belief that the reason why shared_ptr
has a distinction between the managed pointer and the get
pointer is because make_shared
wasn't available. And therefore, if we forced everyone to use make_shared
to create shared_ptr
, we wouldn't need that distinction.
This is incorrect.
You can implement shared_ptr
's pointer-based constructor without that distinction. After all, in the initial creation of a managed shared_ptr
, the get
pointer and the managed pointer are the same. If you wanted shared_ptr
to be the sizeof(T*)
, you could just have the shared_ptr
fetch the get
pointer from the managed block. This is regardless of whether the T
is embedded within the managed block.
So the distinction really has nothing at all to do with make_shared
and its ability to embed the T
within the same memory as the managed block. Or rather, the lack thereof.
No, the distinction between the managed pointer and the get
pointer was created because it added features to shared_ptr
. Important ones. You listed some of them, but you missed others:
The ability to have a shared_ptr
to a base class. That is:
shared_ptr<base> p = make_shared<derived>(...);
To do that, you must have a distinction between what a particular instance points to and what the control block controls.
static_pointer_cast
and dynamic_pointer_cast
(and reinterpret_pointer_cast
in C++17). These all rely on the distinction between the managed pointer and the get
pointer.
enable_shared_from_this
within base classes.A shared_ptr
that points to a member subobject of a type that itself is managed by a shared_ptr
. Again, it requires the managed pointer to not be the same as the get
pointer.
You also seem to trivially dismiss the ability to manage pointers not created by you. That's a critical ability, because it allows you to be compatible with other codebases. Internally, you can use shared_ptr
to manage things made by a library that was written in 1998.
With your way, you divide code into two epochs: pre-C++11, and post-C++11. Your shared_ptr
will do nothing for any code not explicitly written for C++11.
And the thing about wrapping all of these features up into a single type is this:
You don't need another one.
shared_ptr
, because it serves so many needs, can be effectively used almost anywhere. It may not be the absolutely-most-efficient-type-possible, but will do the job in virtually every case. And it isn't exactly slow in doing so.
It handles shared ownership with polymorphism. It handles shared ownership of member objects. It handles shared ownership of memory you didn't allocate. It handles shared ownership of memory with special allocation/deallocation needs. And so forth.
If you need shared-ownership semantics, and you need it to work, shared_ptr
's got your back every time. With your suggested idea, there would always be limitations, something in your way from getting your work done.
A type that works should be preferred by default over one that doesn't.
In hindsight, given
make_shared
, wouldshared_ptr
have a constructor that takes a raw pointer had it been introduced with C++11?
What if you don't control the allocation of the object? What if you need to use a custom deleter? What if you need list-initialization instead of parens?
None of these cases is handled by make_shared
.
Additionally, if you're using weak_ptr
, a shared_ptr
allocated via make_shared
won't free any memory until all the weak_ptr
s are destroyed as well. So even if you have a normal shared pointer where none of the above apply, it's possible that you may still prefer the raw pointer constructor.
Yet another situation would be if your type provides overloads for operator new
and operator delete
. These may make it ill-suited for make_shared
, since those overloads will not be called - and presumably they exist for a reason.
std::shared_ptr
does much more than allocate objects on the heap.
Consider its use as an auto-closing shared file handle:
#include <cstdio>
#include <memory>
int main()
{
auto closer = [](FILE* fp) { std::fclose(fp); };
auto fp = std::shared_ptr<FILE>(std::fopen("foo.txt", "r"),
closer);
}