[Boost-users] [Interprocess] shared_ptr in shared memory and inheritance

578 views
Skip to first unread message

Gaetan Gaumer

unread,
Feb 9, 2009, 12:26:51 PM2/9/09
to Boost...@lists.boost.org
Hello,
we would like to store collections of differents objects derived from a same base class in a shared
memory segment, and use polymorphism to manipulate them using shared_ptr<base>.
But as we can't have virtual functions in shared memory we are exploring this solution :
We designed 2 parallels hierarchies of classes
- A hierarchy of empty objects in process memory with virtual functions (polymorphism) (ex : class A)
- A hierarchy of data objects in shared memory without virtual functions (class A_data)
 
A "process memory" object stores a weak_ptr to the "shared memory" data object.
Use of data are encapsuled in accessors which take care of weak_ptr locking and concurrent access.
 
I don't know if this is a good design (any opinions ?) but it seems to work well... until the destruction
of "shared memory" objects.
The wrong destructor is used (the base one) instead of the destructor of the derived class,
probably because the destructor is not virtual...
So I'm currently stuck and I'm looking for some advices.
 
Here is a little code demonstrating the problem :

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/smart_ptr/shared_ptr.hpp>
#include <iostream>
using namespace boost::interprocess;
using namespace std;

class base {
protected :
int id;
public:
base(int i=0):id(i){};
~base(){cout << "base dtor" << endl;}
int getId() const {return id;}
};

class derived : public base {
protected:
double data;
public:
derived(int i=0, double d=0): base(i), data(d){};
~derived(){cout << "derived dtor" << endl;}
double getData(){return data;}
};

typedef managed_shared_ptr<base, fixed_managed_shared_memory>::type   base_shared_ptr;

int main (){
fixed_managed_shared_memory segment(open_or_create,"sharedPtrSharedMemoryTest", 2048);
// does not compile but that's what I would like
//base_shared_ptr baseSP = make_managed_shared_ptr(segment.construct<derived>(anonymous_instance)(2,3.0), segment);
// turnaround
base_shared_ptr baseSP = make_managed_shared_ptr((base*)segment.construct<derived>(anonymous_instance)(2,3.0), segment);
    cout << "baseSP->getId() =" << baseSP->getId();
    cout <<", baseSP->getData() = " << static_pointer_cast<derived>(baseSP)->getData() << endl;
    baseSP.reset(); // don't know why but base dtor is called 3 times !!!
}

I can live with static_pointer_cast<> because they'll be hidden in accessors.
But I really need that the right destructor (here ~derived()) will be called as it's cause some asserts in real case :
include/boost/interprocess/detail/segment_manager_helper.hpp:176: static boost::interprocess::detail::block_header* boost::interprocess::detail::block_header::block_header_from_value(const void*, size_t, size_t): Assertion `hdr->m_value_alignment == algn' failed.

The commented call to make_managed_shared_ptr makes gcc complains with (thanks gfilt for formatting) :

sharedPtrSharedMemoryTest.cc:30: error: conversion from 
   `boost::interprocess::shared_ptr<
        derived
      , boost::interprocess::allocator<
            void
          , boost::interprocess::segment_manager<
                char
              , boost::interprocess::rbtree_best_fit<
                    boost::interprocess::mutex_family, void *, 0u
                >, boost::interprocess::iset_index
            > 
        >, boost::interprocess::deleter<
            derived
          , boost::interprocess::segment_manager<
                char
              , boost::interprocess::rbtree_best_fit<
                    boost::interprocess::mutex_family, void *, 0u
                >, boost::interprocess::iset_index
            > > >' to non-scalar type 
   `boost::interprocess::shared_ptr<
        base
      , boost::interprocess::allocator<
            void
          , boost::interprocess::segment_manager<
                char
              , boost::interprocess::rbtree_best_fit<
                    boost::interprocess::mutex_family, void *, 0u
                >, boost::interprocess::iset_index
            > 
        >, boost::interprocess::deleter<
            base
          , boost::interprocess::segment_manager<
                char
              , boost::interprocess::rbtree_best_fit<
                    boost::interprocess::mutex_family, void *, 0u
                >, boost::interprocess::iset_index
            > 
        > 
    >' requested

Any help will be welcome.
Gaetan

Emil Dotchevski

unread,
Feb 9, 2009, 12:51:17 PM2/9/09
to boost...@lists.boost.org
You're probably doing something wrong, shared_ptr doesn't require
virtual destructor to call the correct destructor. Here is a working
example: http://codepad.org/sktELTDk

Emil Dotchevski
Reverge Studios, Inc.
http://www.revergestudios.com/reblog/index.php?n=ReCode

> _______________________________________________
> Boost-users mailing list
> Boost...@lists.boost.org
> http://lists.boost.org/mailman/listinfo.cgi/boost-users
_______________________________________________
Boost-users mailing list
Boost...@lists.boost.org
http://lists.boost.org/mailman/listinfo.cgi/boost-users

Ion Gaztañaga

unread,
Feb 9, 2009, 4:00:57 PM2/9/09
to Boost User List
Gaetan Gaumer wrote:
> Hello,
> we would like to store collections of differents objects derived from a
> same base class in a shared
> memory segment, and use polymorphism to manipulate them using
> shared_ptr<base>.
> But as we can't have virtual functions in shared memory we are exploring
> this solution :
> We designed 2 parallels hierarchies of classes
> - A hierarchy of empty objects in process memory with virtual functions
> (polymorphism) (ex : class A)
> - A hierarchy of data objects in shared memory without virtual functions
> (class A_data)
>
> A "process memory" object stores a weak_ptr to the "shared memory" data
> object.
> Use of data are encapsuled in accessors which take care of weak_ptr
> locking and concurrent access.
>
> I don't know if this is a good design (any opinions ?) but it seems to
> work well... until the destruction
> of "shared memory" objects.
> The wrong destructor is used (the base one) instead of the destructor of
> the derived class,
> probably because the destructor is not virtual...
> So I'm currently stuck and I'm looking for some advices.

First of all, if you use fixed_managed_shared_memory, you want to
specify the same base address in ever process, otherwise, you're code
won't work.

You solve your issue, I'm afraid you will need to build a table of
pointers to destructors indexed by a unique number per type (the same
number in all processes), so that when the base class destructor is
called, it makes a lookup in the table and calls the correct
"destructor" function. Not easy, but I think it's the only way. Your
hierarchy couldn't be expanded by users (they need to register their
destructor). It's a limited type of "polymorphism".

Regards,

Ion

Ion Gaztañaga

unread,
Feb 9, 2009, 4:01:32 PM2/9/09
to Boost User List
Emil Dotchevski wrote:
> You're probably doing something wrong, shared_ptr doesn't require
> virtual destructor to call the correct destructor. Here is a working
> example: http://codepad.org/sktELTDk

Yes, it needs a virtual destructor if it's a shared_ptr to the base
class, otherwise you can't achieve type erasure. This virtual call does
not happen with boost::interprocess::shared_ptr because virtuality is
forbidden in shared memory.

Regards,

Ion

Gaetan Gaumer

unread,
Feb 10, 2009, 3:47:10 AM2/10/09
to boost...@lists.boost.org


2009/2/9 Ion Gaztañaga <igazt...@gmail.com>

Emil Dotchevski wrote:
You're probably doing something wrong, shared_ptr doesn't require
virtual destructor to call the correct destructor. Here is a working
example: http://codepad.org/sktELTDk

Yes, it needs a virtual destructor if it's a shared_ptr to the base class, otherwise you can't achieve type erasure. This virtual call does not happen with boost::interprocess::shared_ptr because virtuality is forbidden in shared memory.

Regards,

Ion

In boost::shared_ptr doc
It's said :
"This constructor has been changed to a template in order to remember the actual pointer type passed. The destructor will call delete with the same pointer, complete with its original type, even when T does not have a virtual destructor, or is void."

So I thought Emil is right and that's why I tried :

base_shared_ptr baseSP = make_managed_shared_ptr(segment.construct<derived>(anonymous_instance)(2,3.0), segment);

I even tried something like this :
base_shared_ptr baseSP = base_shared_ptr(segment.construct<derived>(anonymous_instance)(1,3.0),
                                     segment.get_allocator<void>(),
                                     segment.get_deleter<derived>() );

But this does not compile. 
But as boost::interprocess::shared_ptr is based on boost::shared_ptr I thought it could work....

Is it really impossible to achieve the same behavior in boost::interprocess::shared_ptr than in boost::shared_ptr ?
I looked at the code (of both shared_ptr.hpp) and I don't see where is the lock.
But I'm surely not enough skilled to write the right ctor by myself. 

But It would solve my problem, isnt'it  ?
Gaetan
PS : By the way, thanks Ion and others for this great work on boost in general and the Interprocess library in particular.

Ion Gaztañaga

unread,
Feb 10, 2009, 12:08:45 PM2/10/09
to Boost User List
Gaetan Gaumer wrote:
> In boost::shared_ptr doc
> http://www.boost.org/doc/libs/1_38_0/libs/smart_ptr/shared_ptr.htm#Members
> It's said :
> "This constructor has been changed to a template in order to remember
> the actual pointer type passed. The destructor will call *delete* with
> the same pointer, complete with its original type, even when *T* does
> not have a virtual destructor, or is *void*."

Sorry, I spoke too fast. I was trying to say that if you cast your type
to the base class and they construct a shared_ptr<Base>, then you loose
all your polymorphism. shared_ptr's template constructor does this for
you: shared_ptr ptr only knows the real type of the object in that
constructor and that compile-time information is going to disappear when
the templated constructor ends, so shared_ptr constructs a polymorphic
shared_count that will call the correct deleter when needed:

template<class Y> explicit shared_count( Y * p ): pi_( 0 )
{
//sp_counted_impl_p has VIRTUAL functions
pi_ = new sp_counted_impl_p<Y>( p );

//...
}

sp_counted_impl_p has type Y so it calls Y's constructor and
sp_counted_impl_p is polymorphic so it can be casted to sp_counted_base
without loosing functionality. So you always need virtual functions to
get type erasure with shared_ptr. And that's why you can't do this with
interprocess's shared_ptr.

> Is it really impossible to achieve the same behavior in
> boost::interprocess::shared_ptr than in boost::shared_ptr ?

I afraid you can't. shared memory forbids any type of run-time
polymorphism and that includes shared_ptr.

> PS : By the way, thanks Ion and others for this great work on boost in
> general and the Interprocess library in particular.

Thanks for using Boost in general and Interprocess in particular ;-)

Gaetan Gaumer

unread,
Feb 12, 2009, 6:08:33 AM2/12/09
to boost...@lists.boost.org

First of all, if you use fixed_managed_shared_memory, you want to specify the same base address in ever process, otherwise, you're code won't work.

Is it enough to create the segment with the same name in each process :
eg  a call to the  line below  in each process :
fixed_managed_shared_memory segment(open_or_create,"sharedPtrSharedMemoryTest", 2048);

You solve your issue, I'm afraid you will need to build a table of pointers to destructors indexed by a unique number per type (the same number in all processes), so that when the base class destructor is called, it makes a lookup in the table and calls the correct "destructor" function. Not easy, but I think it's the only way. Your hierarchy couldn't be expanded by users (they need to register their destructor). It's a limited type of "polymorphism".

I understand your solution but I don't know wich method I have to redefine.
Because the problem is not really the destructor, but rather the "deleter".
And I don't find how to redefine (and register) the base deleter to call the derived one. 

Could you help me (again...)

Regards,
Gaëtan

Ion Gaztañaga

unread,
Feb 12, 2009, 4:42:18 PM2/12/09
to Boost User List
Gaetan Gaumer wrote:
>
> First of all, if you use fixed_managed_shared_memory, you want to
> specify the same base address in ever process, otherwise, you're
> code won't work.
>
>
> Is it enough to create the segment with the same name in each process :
> eg a call to the line below in each process :
> fixed_managed_shared_memory
> segment(open_or_create,"sharedPtrSharedMemoryTest", 2048);

You don't have any guarantee that the OS will map it in the same address
in both processes.

> I understand your solution but I don't know wich method I have to redefine.
> Because the problem is not really the destructor, but rather the "deleter".
> And I don't find how to redefine (and register) the base deleter to call
> the derived one.
>
> Could you help me (again...)

Sorry, it was just a fast idea that I haven't elaborated. But I guess
this will be hard to implement. Inheritance and shared memory don't work
very well

> Regards,
> Gaëtan

Gaetan Gaumer

unread,
Feb 17, 2009, 9:58:02 AM2/17/09
to boost...@lists.boost.org


2009/2/12 Ion Gaztañaga <igazt...@gmail.com>

Gaetan Gaumer wrote:

   First of all, if you use fixed_managed_shared_memory, you want to
   specify the same base address in ever process, otherwise, you're
   code won't work.

 Is it enough to create the segment with the same name in each process :
eg  a call to the  line below  in each process :
fixed_managed_shared_memory segment(open_or_create,"sharedPtrSharedMemoryTest", 2048);

You don't have any guarantee that the OS will map it in the same address in both processes.

 
Ok, I'll force the mapping to the same address for all the processes. 
 

I understand your solution but I don't know wich method I have to redefine.
Because the problem is not really the destructor, but rather the "deleter".
And I don't find how to redefine (and register) the base deleter to call the derived one.
Could you help me (again...)

Sorry, it was just a fast idea that I haven't elaborated. But I guess this will be hard to implement. Inheritance and shared memory don't work very well

I tried to implement that solution by writing a customDeleter inspired by boost::interprocess::deleter
where the only difference is the operator() :

template<class T, class SegmentManager> void customDeleter<T, SegmentManager>::operator()(const pointer &p){
   int objectId = detail::get_pointer(p)->getId();
   switch (objectId){
   case DERIVED_ID:
  mp_mngr->destroy_ptr((derived*)(detail::get_pointer(p))); //cast to the right type
  break;
   default:
  mp_mngr->destroy_ptr(detail::get_pointer(p));
   break;
   }
}

Then I use it like this :

typedef fixed_managed_shared_memory::segment_manager                    segment_manager_t;
typedef allocator<void, segment_manager_t>                              void_allocator;
typedef customDeleter<base,segment_manager_t> base_Deleter;
typedef shared_ptr<base, void_allocator, base_Deleter>   base_shared_ptr;


base_shared_ptr baseSP = base_shared_ptr((dbSharableData*)segment.construct<derived>(anonymous_instance)(DERIVED_ID,3.3)
     ,segment.get_allocator<void>(),
     base_Deleter(base_Deleter::segment_manager_pointer(segment)) );

This seems to work because the right deleter is called, and assert() in  block_header_from_value()  don't fail as before.
But my program get locked in a scoped_lock at boost/interprocess/mem_algo/rbtree_best_fit.hpp:1264 :
template<class MutexFamily, class VoidPointer, std::size_t MemAlignment>
void rbtree_best_fit<MutexFamily, VoidPointer, MemAlignment>::deallocate(void* addr)
{
   if(!addr)   return;
   //-----------------------
   boost::interprocess::scoped_lock<interprocess_mutex> guard(m_header);
   //-----------------------
   return this->priv_deallocate(addr);
}

I don't understand how and why there's such a lock as I work currently with only one process.
Any hint ?

Regards,
Gaëtan
Reply all
Reply to author
Forward
0 new messages