In the code below function f()
can call the operator bool()
and operator *()
member functions of unique_ptr<C>
for the incomplete class C
. However when function g()
tries to call those same member functions for unique_ptr<X<C>>
, the compiler suddenly wants a complete type and tries to instantiate X<C>
, which then fails. For some reason unique_ptr<X<C>>::get()
does not cause template instantiation and compiles correctly as can be seen in function h()
. Why is that? What makes get()
different from operator bool()
and operator *()
?
#include <memory>
class C;
std::unique_ptr<C> pC;
C& f() {
if ( !pC ) throw 0; // OK, even though C is incomplete
return *pC; // OK, even though C is incomplete
}
template <class T>
class X
{
T t;
};
std::unique_ptr<X<C>> pX;
X<C>& g() {
if ( !pX ) throw 0; // Error: 'X<C>::t' uses undefined class 'C'
return *pX; // Error: 'X<C>::t' uses undefined class 'C'
}
X<C>& h() {
if ( !pX.get() ) throw 0; // OK
return *pX.get(); // OK
}
class C {};
Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.
The unique_ptr class supersedes auto_ptr , and can be used as an element of C++ Standard Library containers. Use the make_unique helper function to efficiently create new instances of unique_ptr .
std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope. The object is disposed of, using the associated deleter when either of the following happens: the managing unique_ptr object is destroyed.
std::unique_ptr::unique_ptrThe object is empty (owns nothing), with value-initialized stored pointer and stored deleter.
Here's a contrived and simplified example, using only our own types:
class Incomplete;
template <class T>
struct Wrap {
T t;
};
template <class T>
struct Ptr {
T* p;
void foo() { }
};
template <class T>
void foo(Ptr<T> ) { }
int main() {
Ptr<Incomplete>{}.foo(); // OK
foo(Ptr<Incomplete>{}); // OK
Ptr<Wrap<Incomplete>>{}.foo(); // OK
::foo(Ptr<Wrap<Incomplete>>{}); // OK!
foo(Ptr<Wrap<Incomplete>>{}); // error
}
The problem is, when we make an unqualified call to foo
, as opposed to a qualified call to ::foo
or calling the member function Ptr<T>::foo()
, we're triggering argument-dependent lookup.
ADL will look in the associated namespaces of template types in class template specializations, which will trigger implicit template instantiation. Template instantiation needs to be triggered in order to perform ADL lookup because, for instance, Wrap<Incomplete>
could declare a friend void foo(Ptr<Wrap<Incomplete >> )
which would need to be invoked. Or Wrap<Incomplete>
might have dependent bases whose namespaces need to also be considered. Instantiation at this point makes the code ill-formed because Incomplete
is an incomplete type and you can't have a member of an incomplete type.
Getting back to the original question, the calls to !pX
and *pX
invoke ADL which leads to the instantation of X<C>
which is ill-formed. The call to pX.get()
does not invoke ADL, which is why that one works fine.
See this answer for more details, also CWG 557.
It's not the unique_ptr
that requires the complete type, it's your class X
that does.
std::unique_ptr<C> pC;
You don't actually do any allocation yet for C
so the compiler doesn't need to know the specifics of C
here.
std::unique_ptr<X<C>> pX;
Here, you use C
as a template type for X
. Because X
contains an object of type T
which is C
here the compiler needs to know what to allocate when X
is instantiated. (t
is an object and thus instantiated on construction). Change T t;
to T* t;
and the compiler wouldn't complain.
Edit:
This does not explain why h() compiles, yet g() does not.
This example compiles fine:
#include <memory>
class C;
std::unique_ptr<C> pC;
C& f() {
if (!pC) throw 0; // OK, even though C is incomplete
return *pC; // OK, even though C is incomplete
}
template <class T>
class X
{
T t;
};
std::unique_ptr<X<C>> pX;
typename std::add_lvalue_reference<X<C>>::type DoSomeStuff() // exact copy of operator*
{
return (*pX.get());
}
void g() {
if ((bool)pX) return;
}
class C {};
int main()
{
auto z = DoSomeStuff();
}
Which makes it even more interesting as this mimics the operator*
but does compile. Removing the !
from the expression also works. This seems to be a bug in multiple implementations (MSVC, GCC, Clang).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With