I have a factory function in factory.h which returns an std::shared_ptr
to a base class in foo.h. factory.h uses
So std::shared_ptr
is three things wrapped up in one small neat package.
First, it is a pointer to some data, with overrides to give you access to it that make using it much like using a raw C pointer.
The second part is a reference counter. It keeps track of the equivalent of an std::atomic<unsigned>
that all std::shared_ptr
copies off the first increment/decrement as they come into / go out of scope.
The third part is a destruction function -- or a pointer to said destruction function, to be more exact.
When you copy a std::shared_ptr
from another, it uses the reference counter/destruction function/pointer to data of the source std::shared_ptr
. So it does not need to write a destruction function, nor does it need to know what the destruction function does!
When you create a std::shared_ptr<Foo>
without copying it, it must create the destruction function. The default destruction function is one that calls the destructor of Foo
: which requires the full declaration of Foo
!
std::shared_ptr
will call the destruction function even if the pointer is nullptr
, so even in the empty case it need write that destruction function.
So the short story is: anywhere you create a std::shared_ptr<Foo>
not by copying or moving from another std::shared_ptr
, you must either supply a manual destruction function, or you must have Foo::~Foo
visible (ie, the full declaration of Foo
).
The cases where you called a function that returned a std::shared_ptr
copied them from inside the body of the function: within that body, wherever those std::shared_ptr
eventually came from, one of the two requirements above where met.
The case where you simply create it on nullptr
it must write and store the destruction function, and that requires full access to the class.
If you really need to std::shared_ptr<foo> ptr(nullptr);
without access to foo
, try std::shared_ptr<foo> ptr(nullptr, [](foo*){});
, which should pass in a do-nothing destruction function.
I'm afraid your question is exactly backwards. :-)
In the second example, where you're calling create_foo_A() and create_foo_B(), you're essentially promising the compiler that these functions, when linked into a program, will return pointers to instances of classes derived from foo. The compiler doesn't need to know or care what makes up a foo; at this point, it's up to those functions to decide what a foo really is.
But in your first example, the line:
std::shared_ptr<foo> ptr(nullptr);
constructs a shared pointer to a foo, and the program is expecting to find a destructor for the the shared pointer to call. Since you haven't defined foo, only declared it's a class, the compiler doesn't know what the class looks like (it's an incomplete type) and complains about not having that destructor.
This:
std::shared_ptr<foo> ptr(nullptr);
should not warn. It is a perfectly valid statement, and has no need to know the complete type of foo
. It is likely that it is warning for you because nullptr
is being emulated. Neither should this warn:
std::shared_ptr<foo> ptr;
and the two are specified to be equivalent:
constexpr shared_ptr(nullptr_t) : shared_ptr() { }
In this scene, why doesn't std::shared_ptr need to know the complete type of class foo?
The definitions of create_foo_A
and create_foo_B
will need to know the complete type of foo
. But the declarations do not.
In a nutshell, the shared_ptr<T>::shared_ptr(U*)
constructor needs to know the complete definition of U
. But little else does. There is a more complete survey in this answer:
https://stackoverflow.com/a/6089065/576911
So if you said instead:
std::shared_ptr<foo> ptr((foo*)0);
Then you have undefined behavior. Apparently VC++ will warn. libc++ gives a hard error. Whereas using nullptr
is quite ok, at least according to C++11.
In summary, none of your examples require the complete definition of foo
.
[util.smartptr.shared.dest]
~shared_ptr();
Effects:
*this
is empty or shares ownership with another shared_ptr
instance (use_count() > 1
), there are no side effects.[util.smartptr.shared.const]
constexpr shared_ptr() noexcept;
Effects: Constructs an empty shared_ptr
object.
[util.smartptr.shared]
In the synopsis:
constexpr shared_ptr(nullptr_t) : shared_ptr() { }