So when using shared_ptr
you can write:
shared_ptr var(new Type());
I wonder why they didn\'t allow a
The syntax:
shared_ptr<Type> var = new Type();
Is copy initialization. This is the type of initialization used for function arguments.
If it were allowed, you could accidentally pass a plain pointer to a function taking a smart pointer. Moreover, if during maintenance, someone changed void foo(P*)
to void foo(std::shared_ptr<P>)
that would compile just as fine, resulting in undefined behaviour.
Since this operation is essentially taking an ownership of a plain pointer this operation has to be done explicitly. This is why the shared_ptr
constructor that takes a plain pointer is made explicit
- to avoid accidental implicit conversions.
The safer and more efficient alternative is:
auto var = std::make_shared<Type>();
I wonder why they didn't allow a much simpler and better...
Your opinion will change as you become more experienced and encounter more badly written, buggy code.
shared_ptr<>
, like all standard library objects is written in such as way as to make it as difficult as possible to cause undefined behaviour (i.e. hard to find bugs that waste everyone's time and destroy our will to live).
consider:
#include<memory>
struct Foo {};
void do_something(std::shared_ptr<Foo> pfoo)
{
// ... some things
}
int main()
{
auto p = std::make_shared<Foo>(/* args */);
do_something(p.get());
p.reset(); // BOOM!
}
This code cannot compile, and that's a good thing. Because if it did, the program would exhibit undefined behaviour.
This is because we'd be deleting the same Foo twice.
This program will compile, and is well-formed.
#include<memory>
struct Foo {};
void do_something(std::shared_ptr<Foo> pfoo)
{
// ... some things
}
int main()
{
auto p = std::make_shared<Foo>(/* args */);
do_something(p);
p.reset(); // OK
}
The issue with allowing a raw pointer to be implicitly converted into a std::shared_ptr
can be demonstrated with
void foo(std::shared_ptr<int> bar) { /*do something, doesn't matter what*/ }
int main()
{
int * bar = new int(10);
foo(bar);
std::cout << *bar;
}
Now if the implicit conversion worked the memory bar
points to would be deleted by the shared_ptr
destructor at the end of the foo()
. When we go to access it in std::cout << *bar;
we now have undefined behavior as we are dereferencing a deleted pointer.
In your case you create the pointer directly at the call site so it does not matter but as you can see from the example it can cause problems.
Allowing this allows you to call functions with pointer arguments directly, which is error prone because you're not necessarily aware at call site that you're creating a shared pointer from it.
void f(std::shared_ptr<int> arg);
int a;
f(&a); // bug
Even if you disregard this, you create the invisible temporary at the call site, and creating shared_ptr
is quite expensive.
Why [doesn't]
shared_ptr
permit direct assignment [copy initialization]?
Because it is explicit
, see here and here.
I wonder what the rationale [is] behind it? (From a comment now removed)
TL;DR, making any constructor (or cast) explicit
is to prevent it from participating in implicit conversion sequences.
The requirement for the explicit
is better illustrated with the shared_ptr<>
is an argument for a function.
void func(std::shared_ptr<Type> arg)
{
//...
}
And called as;
Type a;
func(&a);
This would compile, and as written and is undesired and wrong; it won't behave as expected.
It gets further complicated with adding user defined (implicit) conversions (casting operators) into the mix.
struct Type {
};
struct Type2 {
operator Type*() const { return nullptr; }
};
Then the following function (if not explicit) would compile, but offers a horrible bug...
Type2 a;
func(a);