Hi experts!
I have the following code which I use for dynamically creating objects in one module (executable or shared object (.so or .dll)) and be able to delete it from any other module while enforcing the call to the delete operator in the original module where the object was created (such that the same Heap is used for sure). This is essential for being able to easily use the shared_ptr across the modules while not having to care about in what module the object ends up being deleted by the depletion of the shared_ptr reference count.
However, I am not entirely happy with the semantics of it and also with the fact that it would be even nicer if the old-fashion delete operator could be used directly (instead of a "scoped_deleter" callable function object). I tried other alternatives, such as the one proposed here in the "final implementation". But all my trials at overloading the delete operator for that purpose has resulted in segmentation faults, pure virtual method call crashes, or worse.
So far, I have not really used my library (for which these two classes and a few others form the basis of) in different modules, only in different static libraries. So I would like your opinion on this before it gets too "carved in stone". I know there are people here with a bigger bag-a-tricks than me, and I look forward to hear some good suggestions. Thanks in advance.
/**
*\file rk_shared_object_base.hpp
*
* This library declares an underlying class that is the base for the ReaK::shared_object class.
* This header and its contents are not meant to be included are directly used by end users
* except for the null_deleter and scoped_deleter for use in setting up shared pointers with
* either no deletion on destruction of the last shared pointer or a deletion that is in the
* same module scope as the code that created the shared pointer (this is the most usual,
* although for a single module project it has no real effect).
*
* \author Mikael Persson (mikael.s.persson@gmail.com)
* \date february 2010
*/
#ifndef RK_SHARED_OBJECT_BASE_HPP
#define RK_SHARED_OBJECT_BASE_HPP
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include "rk_defs.hpp"
#include <iostream>
namespace ReaK {
/**
* This structure is a simple callable structure that does nothing. It acts as a place-holder for
* a special deleter for a shared_ptr, in this case this "null deleter" simply does not delete anything.
* This is useful for an object for which you want a shared_ptr to, but that is not going to be
* deleted by this shared_ptr branch (i.e. will be deleted by another shared_ptr branch). This can
* be used to break cycles in the object hierarchy.
*/
struct null_deleter
{
void operator()(void const *) const
{
}
};
/**
* This is a base class for the ReaK::shared_object. This holds are "null deleted" shared_ptr to
* itself as well as a basic virtual destroy function that is used to ensure proper scoping of the
* object deletion. Those two features allow the descendant-class objects to deliver a weak_ptr
* to themselves (that will expire when the object is destroyed) and to be shared across modules
* without deleter module scope issues (i.e. it will be deleted from the same compiled library
* from which is got created with its own vtable).
*/
class shared_object_base {
protected:
boost::shared_ptr<shared_object_base> mThis;
public:
virtual void RK_CALL destroy() = 0;
/**
* This method returns a weak_ptr to this object. The weak pointer will expire as the object gets
* deleted.
* \note This pointer is weaker than a regular weak pointer because locking this pointer does not
* guarantee that it remains for as long as the locked pointer exists so this is more intended
* for use as a test pointer to see if the object still exists. If a real shared ownership
* scheme is desired, the shared pointer should be obtained from an owner of this object.
* \return weak pointer to this object. A lock on this pointer will not give shared ownership and
* should be regarded as a momentary access to the object with no guarantee that it will
* not get deleted in the meantime. A real shared ownership can only be obtained from the
* actual owner of this object.
*/
boost::weak_ptr<shared_object_base> RK_CALL getWeakPtr() const { return mThis; };
shared_object_base() : mThis(this,null_deleter()) {};
virtual ~shared_object_base() { RK_NOTICE(8,"Shared object base destructor reached!");};
};
/**
* This structure is a simple callable structure that deletes an object by calling the virtual "destroy"
* method. It acts as a special deleter for a shared_ptr, in this case this "scoped deleter" simply makes
* sure the object is deleted via its vtable and thus will go to the code in the same executable scope
* from which it was created and thus making the shared_ptr movable between executable modules.
*/
struct scoped_deleter {
void operator()(shared_object_base * p) const {
RK_NOTICE(8,"Shared object base scoped deleter reached!");
p->destroy();
};
};
};
#endif
/**
*\file rk_shared_object.hpp
*
* This library declares the basic class "ReaK::shared_object" which allows all descendants
* to be safely shared across executable modules (given the binary compatibility of the boost
* smart pointers is kept, this is out of the my control). All classes in the ReaK platform
* should at least be a descendant of this class (except some low-level classes, but no end-user
* defined classes). Usually, however, end-users will prefer the ReaK::named_object class which
* adds a name to the objects (and it is the next descendant after ReaK::shared_object).
*
* \author Mikael Persson (mikael.s.persson@gmail.com)
* \date february 2010
*/
#ifndef rk_shared_obj_H
#define rk_shared_obj_H
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <vector>
#include "rk_defs.hpp"
#include "rk_serializable.hpp"
#include "rk_shared_object_base.hpp"
#include <iostream>
namespace ReaK {
/**
* This basic class allows all descendants
* to be safely shared across executable modules (given the binary compatibility of the boost
* smart pointers is kept, but this is out of the my control). All classes in the ReaK platform
* should at least be a descendant of this class (except some low-level classes, but no end-user
* defined classes). Usually, however, end-users will prefer the ReaK::named_object class which
* adds a name to the objects (and it is the next descendant after ReaK::shared_object).
*/
class shared_object : public shared_object_base, public serialization::serializable {
public:
virtual void RK_CALL destroy() { RK_NOTICE(8,"Shared object destroy method reached!"); delete this; };
virtual ~shared_object() { RK_NOTICE(8,"Shared object destructor reached!"); };
RK_RTTI_MAKE_CLASS_TYPE(shared_object)
RK_RTTI_MAKE_CASTABLE_1BASE(shared_object,serialization::serializable)
};
class empty_base_object {
};
namespace rtti {
/**
* This function replaces the standard C++ dynamic cast (i.e. dynamic_cast<>()) and furthermore, also
* replaces the boost::shared_ptr dynamic cast (i.e. boost::dynamic_pointer_cast<>()). This new function
* for dynamic casting is required in order for ReaK::rtti system to take precedence over the C++ RTTI
* because, unlike the C++ standard version of RTTI, this implementation will work across executable modules,
* and thus, allow objects to be shared between modules with full dynamic up- and down- casting capabilities.
* \note this function is a special overload for the shared_object_base class pointers, the general version is
* found in the "rk_typed_object.hpp" library, as part of the ReaK::rtti system.
*/
template <class Y>
boost::shared_ptr<Y> rk_dynamic_ptr_cast(boost::shared_ptr<ReaK::shared_object_base> p) {
return boost::shared_ptr<Y>(p,reinterpret_cast<Y*>(boost::dynamic_pointer_cast<ReaK::shared_object>(p)->castTo(Y::StaticTypeID)));
};
};
};
#endif