Specs for shared_ptr; compatibility between classic pointers and shared_ptr
Hello,
recently, I posted a question about a difference in behaviour between
regular pointers and shared_ptrs on comp.lang.c++ . Maybe the outcome of
the conversation is relevant to someone here, so posting a summary here.
The following code works:
---------------------------------------------
struct BaseA
{
int x;
};
struct BaseB
{
double x;
};
struct DerivA : public BaseA
{
int y;
};
struct DerivB : public BaseB
{
double y;
};
struct S
{
S(BaseA * pa_) : pa(pa_), pb(0) {}
S(BaseB * pb_) : pa(0), pb(pb_) {}
BaseA * pa;
BaseB * pb;
};
int main()
{
S s(new DerivA()); // works fine
delete s.pa;
return 0;
}
---------------------------------------------
When we replace classical pointers with shared_ptrs, it does not work
anymore:
--------------------------------------------------
#include <tr1/memory>
struct BaseA
{
int x;
};
struct BaseB
{
double x;
};
struct DerivA : public BaseA
{
int y;
};
struct DerivB : public BaseB
{
double y;
};
struct S
{
S(std::tr1::shared_ptr<BaseA> pa_) : pa(pa_) {}
S(std::tr1::shared_ptr<BaseB> pb_) : pb(pb_) {}
std::tr1::shared_ptr<BaseA> pa;
std::tr1::shared_ptr<BaseB> pb;
};
int main()
{
// S s(std::tr1::shared_ptr<BaseA>(new DerivA()) ); // works
// S s( new DerivA() ); // Doesn't work, SP constructor is explicit
S s(std::tr1::shared_ptr<DerivA>(new DerivA()) ); // breaks
return 0;
}
--------------------------------------------------
Essentially, it breaks because the polymorphism works in function
arguments, but not in template arguments. Shared_ptrs here function
according to the specs of tr1, and the code does not compile.
Kai-Uwe Bux commented the following:
--------------- Begin Quote ----------------
Could anyone comment on this ? Should option #3 with shared_ptr<DerivA>
work or not ?
According to the technical report TR1, it should not work.
Conceptually, I believe so. I can understand that the platform could
have some trouble with it, if it doesn't recognize that the template
parameter DerivA in shared_ptr<DerivA> is derived of BaseA.
You have a point, but it would require changing the specs of shared_ptr.
To simplify the exposition, let us consider the following class:
template < typename T >
struct pointer_to {
T * the_ptr;
pointer_to ( T * ptr )
: the_ptr ( ptr )
{}
T & operator* ( void ) const {
return ( *the_ptr );
}
T * operator-> ( void ) const {
return ( the_ptr );
}
template < typename D >
pointer_to ( pointer_to<D> const & d_pointer )
: the_ptr ( d_pointer.the_ptr )
{}
};
There is a conversion operator that allows to copy construct a pointer_to<T>
from a pointer to any derived class. Attempts to copy construct from
non-derived classes will fail when the compiler encounters the body of the
conversion operator. With this setup, the following will be ambiguous:
struct X {};
struct XD : public X {};
struct Y {};
struct YD : public Y {};
void f ( pointer_to<X> xp ) {}
void f ( pointer_to<Y> yp ) {}
int main ( void ) {
pointer_to<YD> ydp ( new YD );
f( ydp );
}
The reason is that the compiler sees two possible conversions and it is not
supposed to check whether only one of them can be compiled cleanly.
Now, there is a way to guide the compiler in these issues. But it requires
some serious scaffolding:
struct yes_type { char dummy; };
struct no_type { yes_type a; yes_type b; };
template < typename From, typename To >
class is_convertible {
static
From* dummy ( void );
static
yes_type check ( To );
static
no_type check ( ... );
public:
static bool const value =
sizeof( check( *dummy() ) ) == sizeof( yes_type );
}; // is_convertible
template < bool b, typename T >
struct enable_if;
template < typename T >
struct enable_if<true,T> { typedef T type; };
template < typename T >
struct pointer_to {
T * the_ptr;
pointer_to ( T * ptr )
: the_ptr ( ptr )
{}
T & operator* ( void ) const {
return ( *the_ptr );
}
T * operator-> ( void ) const {
return ( the_ptr );
}
template < typename D >
pointer_to ( pointer_to<D> const & d_pointer,
typename enable_if< is_convertible<D*,T*>::value, void*
::type
p = 0 )
: the_ptr ( d_pointer.the_ptr )
{}
};
With this setup, the above snippet will compile cleanly since the signature
of the conversion operator is enough to tell the compiler that there is
only one possible conversion.
--------------- End Quote ----------------
So, it would appear that:
- there is a conceptual difference between pointer and shared_ptr
- this difference can be removed with the proposed solution, allowing
greater compatibility and usability of shared_ptr
- but that would require changing the specs of shared_ptr.
Rumor has is that part of the discussion on standadization is now taking
place here, so at least I would like to bring the matter to attention -
although I have no idea whose attention :-)
Best regards,
F. Beekhof
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]