Opened 8 years ago

Closed 8 years ago

Last modified 8 years ago

#10798 closed Bugs (wontfix)

boost::movelib::unique_ptr resolves typedef pointer with the deletor's

Reported by: Matteo Settenvini <matteo.settenvini@…> Owned by: Ion Gaztañaga
Milestone: To Be Determined Component: move
Version: Boost 1.57.0 Severity: Problem
Keywords: Cc:

Description

Hello,

as part of a project which entails adding some sort of virtuality in shared memory, we are using boost::interprocess::unique_ptr extensively; this, starting from 1.57.0, maps directly to boost::movelib::unique_ptr.

I am providing a short example that exposes the problem (see attachment).

We use a generic deleter which is non-virtual and takes a pointer to a common base class for destruction+deallocation with a custom allocator. The deleter must have a "typedef ... pointer" member, since it needs to work also with boost::interprocess::shared_ptr.

However, the current implementation of unique_ptr has also a member "typedef pointer" which indirectly resolves to Deleter::pointer. That means that unique_ptr<Derived>::pointerDeleter::pointerBase.

Since e.g. unique_ptr::operator->() or unique_ptr::get() have a return type of type "pointer", each use of them effectively returns a pointer to the base class, which then needs manual static or dynamic casting.

I am providing also the patch we are currently using to work around the issue. Of course, this is just a quick hack and not suitable for Boost, but it helps to pinpoint the problem.

Attachments (2)

boost-movelib-unique_ptr-deleter-issue.cc (789 bytes ) - added by Matteo Settenvini <matteo.settenvini@…> 8 years ago.
Small example showing the compilation error
003-move-unique_ptr-pointer-type.patch (957 bytes ) - added by Matteo Settenvini <matteo.settenvini@…> 8 years ago.
Temporary workaround patch

Download all attachments as: .zip

Change History (6)

by Matteo Settenvini <matteo.settenvini@…>, 8 years ago

Small example showing the compilation error

by Matteo Settenvini <matteo.settenvini@…>, 8 years ago

Temporary workaround patch

comment:1 by Ion Gaztañaga, 8 years ago

boost::interprocess:unique_ptr was never designed for this use case, at least, not on purpose. It was designed to be nearly-standard compatible and compatible with managed_unique_ptr, which defines a deleter with a pointer typedef, defining a smart pointer to the element_type. Couldn't a new unique_ptrdeleter that derives from your deleter and defines pointer to be a pointer to T, solve the issue?

comment:2 by Matteo Settenvini <matteo.settenvini@…>, 8 years ago

Unfortunately not, since the deleter I wrote uses a field of the base class to determine the runtime type of the object, and resolves a call to the right destructor (plus, deallocates memory with the right custom allocator). This is needed since we have no virtuality in objects of classes that need to be stored in shared memory.

In other words, I need to use just a deleter for the whole hierarchy of classes, and thus its pointer member needs to be the base type. We're rolling out our handmade version of virtuality, with virtual tables and all that goes with them.

What I find counter-intuitive from a design standpoint, is that:

  • be it that we have classes Derived < Base.
  • given a Deleter with a member "pointer" as a typedef to Base.
  • defining a unique_ptr<Derived, Deleter> and invoking ::get() results in a pointer to Base being returned.

What is the technical reason of having the pointer typedef of the unique_ptr using the pointer member of the deleter, instead than rebinding it to allow upcasts?

comment:3 by Ion Gaztañaga, 8 years ago

Resolution: wontfix
Status: newclosed

The reason is that the standard mandates that:

en.cppreference.com/w/cpp/memory/unique_ptr

pointer 	std::remove_reference<Deleter>::type::pointer if that type exists, otherwise T* 

boost::movelib::unique_ptr is an implementation of the standard unique_ptr for boost. The Interprocess implementation had definitely a bug because it was also trying to implement the standard.

I think you can workaround it with a new deleter that inherits from your polymorphic deleter that just forwards it. Something like (simplified example):

template<class Derived>
struct new_deleter
  : public old_deleter
{
  typedef my_pointer<Derived> pointer;
  void operator()(pointer p)
  {  old_deleter::operator()(p);  } //Call polymorphic deleter
};

comment:4 by Matteo Settenvini <matteo.settenvini@…>, 8 years ago

Thanks, but then it wouldn't work with upcasting (and downcasting too). I would end up with a shared_ptr<Derived, new_deleter<Derived>> which is not automatically convertible to a shared_ptr<Base, new_deleter<Base>>.

Note: See TracTickets for help on using tickets.