No, shared_ptr explicitly supports incomplete types. My point
was that even if scoped_ptr didn't, I can't think of any
reason why it could nevertheless be used with incomplete types
as a class member function as long as you implement the
constructor and destructor for that class.
I think there is confusion among a number of different
statements here. And perhaps I misread what you were claiming.
With shared_ptr, it is sufficient that the class be complete
when the pointer is constructed; it is not necessary for it to
be complete when the class is destructed. For scoped_ptr, it is
necessary that the class be complete any time a member function
of scoped_ptr is instantiated. Basically:
Instantiation class must be complete:
shared_ptr other Boost pointers
Declaration: no no
Member functions other
than ctor, reset(): no yes
Constructor, reset(): yes yes
Simply declaring that class A has a member variable named
boost::scoped_ptr<IncompleteType> ptr; is not the same thing
as constructing that member variable. The construction of that
variable happens in the constructor of A, and as long as the
type is not incomplete in that context, I can't think of any
reason why it wouldn't work. Likewise for the destructor.
Agreed. The point I was making is that it must be complete for
the destructor as well (which is not the case with shared_ptr).
Instead of just saying "it might not work", could you please
give me a *precise reason* for it not to work? I want to know.
I don't think that that's the right question. There is only one
"precise reason" some code using a library component might not
work: it fails to meet the requirements specified by the
library.
Exactly what is it that stops it from working?
The second paragraph of section 17.4.3.6 of the standard: "In
particular, the effects are undefined in the following cases:
[...] -- if an incomplete type is used as a template argument
when instantiating a template component." Basically, this
sentence establishes a contract between you and the library
implementor; you guarantee that the type will be complete, and
because of this, the library implementor is free to count on it
being complete.
As long as the type is complete when the constructor of the
class is implemented, I can't think of any reason why it
wouldn't work.
Because the contract says it's not required to work.
In other words, why wouldn't this work?
// A.hh
class ToBeManaged;
class A
{
std::auto_ptr<ToBeManaged> ptr;
// A.cc
#include "A.hh"
#include "ToBeManaged.hh"
A::A() {} // Implicitly calls the constructor of 'ptr'
A::~A() {} // Implicitly calls the destructor of 'ptr'
'ptr' gets constructed here, and in this context ToBeManaged
has been fully declared. Why wouldn't it work?
Because the contract says that it's not required to work. (In
practice, it's likely to work in any implementation which
doesn't use concepts. But that's beside the point. The code
has undefined behavior.)
Likewise for the destructor.
Are you sure those smart pointers can be constructed when the
type is incomplete? Doesn't, for example, boost::shared_ptr
require for the type to be complete when it's constructed?
I said "instantiating the class", not instantiating the member
functions. In my table above, a column for std::auto_ptr would
have yes in every line.
In other words, can you, at least in theory, do this:
class ToBeManaged;
void foo(ToBeManaged* instance)
{
boost::shared_ptr<ToBeManaged> ptr = instance; // Will this compile?
boost::shared_ptr<ToBeManaged> ptr2; // Or even this?
...
}
Whether they will compile or not is an open question. Boost
doesn't guarantee that illegal use will fail to compile (or does
it? I seem to recall seeing something along those lines in the
specification). And the documentation seems a little bit
ambiguous with regards to the second. There is a more or less
general statement which suggests that constructors and reset()
require a complete type, but when you read the exact
specifications for the default constructor, this isn't a
requirement. Interestingly enough, the documentation of reset()
doesn't say that a complete type is required either. But both
reset() and operator=() are defined in terms of a constructor,
so the requirement is indirectly present.
(The labels in my table above are based on the generic statement
in the Boost documentation. It might be more accurate to say
that a complete type is needed when constructing with a ponter,
assignment or reset() with a pointer argument.)
Isn't it, thus, enough for the destructor of the class (which
has a smart pointer as member variable) to have an explicitly
implemented destructor (which is implemented in a context
where the type being managed is complete)?
See my table above. Anytime you instantiate a member function
of boost::scoped_ptr, the class must be complete. You may,
however, instantiate the class template itself on an incomplete
type. The rules as to when and where a template is implicitly
instantiated are fairly complex, but roughly speaking: a class
template will be instantiated any time it is used in a context
where the compiler requires a complete type, and a member
function or static variable of the class template will be
instantiated whenever they are "used".