Re: Dangling pointers Lua style...
Nigel Atkinson <[email protected]>
| Newsgroups | gmane.comp.lang.lua.bind.user |
|---|---|
| Message-ID | <1285910041.1680.31.camel@octavo> |
Ah ha! Eduard Mueller, you have inspired me to my own solution!
Well I'm still testing however.... ;-)
I quite like the look of your solution too. It works similarly but
different!
Mine basically involves a smart pointer that throws on accessing a null
pointer. It does not look after the lifetime of the pointed to object
however - which is what I wanted.
This is enabled by two wrapper funcs around the normal API supplied
Create and Destroy functions. The former stuffs the pointer from the
API Create into a ward_ptr, and the later marks the ward_ptr as
'invalid' after invoking the normal API Destroy.
A difference is there is no deriving from classes.
Code and my test use are attached. If you see any holes - please tell
me! ;-) I had to do some double indirection as the ward_ptr's get copied
around (a lot!), and changes (i.e. invalidation) had to affect all that
where pointing in the same direction. :-) I might change the heap
allocs to use a stl container perhaps... they might get fragmenty after
a bit of use.
Nigel
On Fri, 2010-10-01 at 01:58 +0200, eduard mueller wrote:
> We had the same problem: access to dead objects from within Lua should
> always cleanly fire Lua errors instead of crashing. We ended up in
> using our own weak_ptr impl to solve this. If there are more elegant
> solutions to solve this problem (invalidating all refs in Lua as soon
> as the C++ object dies for example), I would also be very interested
> to hear about this.
>
> Our weak ptr basically works like:
>
> ===
>
> /* baseclass for something a WeakPtr can point to */
>
> class WeakReferenceable
> {
> public:
> virtual ~WeakReferenceable()
> {
> // Invalidate all weakptrs that point to "this" (accesses a static map
> // of all registered WeakReferenceables)
> WeakPtrBase::WeakRefDying(this);
> }
> };
>
>
> /* non templated baseclass for a WeakPtr */
>
> class WeakPtrBase
> {
> public:
> WeakPtrBase(WeakReferenceable* ptr)
> : _weak_ptr(ptr)
> {
> // keep track of WeakPtrBase <-> Weakreferenceable links in a static map
> RegisterWeakRef(this, ptr);
> }
>
> ~WeakPtrBase()
> {
> // get rid of WeakPtrBase <-> Weakreferenceable links in the static map
> UnregisterWeakRef(this, _weak_ptr);
> }
>
> protected:
> virtual void WeakReferenceableDying() = 0;
> };
>
> ===
>
> /* final weak ptr impl. Guaranteed to point to NULL or a valid
> object. Can only wrap WeakReferenceable's */
>
> template <class Class>
> class WeakPtr
> {
> public:
> WeakPtr(Class* ptr)
> : WeakPtrBase(ptr),
> _class_ptr(ptr) { }
>
> private:
> virtual void WeakReferenceableDying()
> {
> // invalidate our pointer to the deleted object
> _class_ptr = NULL;
> }
>
> // ptr access, copy and stuff...
> };
>
>
> ====
>
> Then in luabind:
>
> // hold objects in WeakPtrs always:
>
> class MyClass : public Weakreferenceable { }
>
> class_<MyClass , WeakPtr<MyClass> >("myclass")
>
> // pointer access from luabind can now deal with NULL pointers
>
> template<class T>
> T* get_pointer(WeakPtr<T> const& Ptr)
> {
> if (Ptr.Object() == NULL)
> {
> throw std::logic_error("trying to access an object which is not or
> no longer available.");
> }
>
> return Ptr.Object();
> }
>
> ===
>
> get_pointer above assumes that accessing a NULL WeakPtr is an error.
> If you want to allow NULL access, then this of course can't make the
> Lua objects nil. You would have to add an extra function, like i.g:
> is_ptr_valid(WeakPtr<T>) to check for NULL in Lua...
>
> Hope it helps. Let me know if you are interested in the complete impl...
>
> Greets,
> Eduard
>
>
>
> On Fri, Oct 1, 2010 at 00:52, Nigel Atkinson <[email protected]> wrote:
> > Yes, that would work. Thank you.
> >
> > I was trying to think of a generic solution, such as some sort of
> > "ward_ptr" that threw an exception on getPointer() if it was invalid.
> > However the problem is - how would such a class "know" when the pointer
> > it contains has been free'd. It probably could be done via, heap
> > debugging functions or some such, but that's just yuck. (Slow,
> > non-portable, etc)
> >
> > A wrapper class it is methinks! But hang on moment...
> > would this work?? How clever is Luabind about this...
> >
> > void Destroy( ward_ptr<myclass> ptr )
> > {
> > ptr->Destroy();
> > ptr.invalidate();
> > }
> >
> > class_<myclass, ward_ptr>("myclass")
> > [
> > .def("Create", MakeOne ) // MakeOne would return a myclass*
> > bla bla bla
> > .def("Destroy", Destroy )
> > ]
> >
> > Hmmmmm.
> >
> > Nigel
> >
> > On Thu, 2010-09-30 at 13:17 -0400, Evan Wies wrote:
> >> You can try wrapping the Create/Destroy in a proxy class that uses
> >> constructors/destructors instead? It depends on how pervasive this
> >> idiom is in your code. From there, Luabind is pretty flexible with how
> >> you can bind arbitrary functions to classes.
> >>
> >> If you want to explicitly control lifetimes, check out how Corona deals
> >> with a similar problem:
> >> http://developer.anscamobile.com/content/application-programming-guide-graphics-and-drawing#Removing_Objects_Properly
> >>
> >> -Evan
> >>
> >>
> >> On 09/30/2010 09:28 AM, Nigel Atkinson wrote:
> >> > Consider a bound class without a bound constructor. Instead elsewhere
> >> > there is a bound function to create and another to destroy.
> >> >
> >> > Used something like this...
> >> >
> >> > obj = CreateObject()
> >> >
> >> > -- use the obj
> >> >
> >> > DestroyObject( obj )
> >> >
> >> > The destroy might do more, but ultimately deletes the object. Problem
> >> > is in Lua, we now have a dangling pointer! obj after the destroy call
> >> > still points somewhere, and if you accidentally use it bad things
> >> > happen. Not good if the Lua scripts are from the end user. They can't
> >> > be expected to write "obj = nil" after each call. :)
> >> >
> >> > So how to automatically set obj to nil after a destroy call?
> >> >
> >> > My thoughts lean towards “All problems in computer science can be solved
> >> > by another level of indirection”
> >> >
> >> > In this case some sort of smart/auto pointer, and wrapper functions that
> >> > throw exceptions if the passed pointer points to bad data?
> >> >
> >> > What are everyone's thoughts? I have the feeling I've missed some
> >> > obvious answer.
> >> >
> >> >
> >> > ------------------------------------------------------------------------------
> >> > Start uncovering the many advantages of virtual appliances
> >> > and start using them to simplify application deployment and
> >> > accelerate your shift to cloud computing.
> >> > http://p.sf.net/sfu/novell-sfdev2dev
> >> > _______________________________________________
> >> > luabind-user mailing list
> >> > [email protected]
> >> > https://lists.sourceforge.net/lists/listinfo/luabind-user
> >>
> >>
> >>
> >> ------------------------------------------------------------------------------
> >> Start uncovering the many advantages of virtual appliances
> >> and start using them to simplify application deployment and
> >> accelerate your shift to cloud computing.
> >> http://p.sf.net/sfu/novell-sfdev2dev
> >> _______________________________________________
> >> luabind-user mailing list
> >> [email protected]
> >> https://lists.sourceforge.net/lists/listinfo/luabind-user
> >
> >
> >
> > ------------------------------------------------------------------------------
> > Start uncovering the many advantages of virtual appliances
> > and start using them to simplify application deployment and
> > accelerate your shift to cloud computing.
> > http://p.sf.net/sfu/novell-sfdev2dev
> > _______________________________________________
> > luabind-user mailing list
> > [email protected]
> > https://lists.sourceforge.net/lists/listinfo/luabind-user
> >
>
> ------------------------------------------------------------------------------
> Start uncovering the many advantages of virtual appliances
> and start using them to simplify application deployment and
> accelerate your shift to cloud computing.
> http://p.sf.net/sfu/novell-sfdev2dev
> _______________________________________________
> luabind-user mailing list
> [email protected]
> https://lists.sourceforge.net/lists/listinfo/luabind-user
------------------------------------------------------------------------------
Start uncovering the many advantages of virtual appliances
and start using them to simplify application deployment and
accelerate your shift to cloud computing.
http://p.sf.net/sfu/novell-sfdev2dev
_______________________________________________
luabind-user mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/luabind-user
main.cpp
(text/x-c++src, 1.7 KB)
#include <iostream>
#include <luabind/luabind.hpp>
#include <lua.hpp>
#include "ward_ptr.hpp"
using namespace luabind;
using namespace std;
// From some library for example....
// -----------------8<------------------
class myclass
{
public:
myclass()
{
cout << "myclass created: " << this << endl;
}
~myclass()
{
cout << "myclass destructed: " << this << endl;
}
void dosomething()
{
cout << "myclass:dosomething()" << endl;
}
};
// Normal API functions to make a 'myclass'
myclass* Create()
{
return new myclass;
}
void Destroy( myclass* ptr )
{
delete ptr;
}
// ----------------8<---------------------
// Couple of glue functions...
ward_ptr<myclass> CreateWard()
{
return ward_ptr<myclass>( Create() );
}
void DestroyWard( ward_ptr<myclass> ptr )
{
Destroy( ptr.get() );
ptr.invalidate();
}
int main( int argc, char **argv )
{
lua_State* L = lua_open();
luaL_openlibs( L );
open( L );
module(L)
[
class_<myclass, ward_ptr<myclass> >("myclass")
.def( "dosomething", &myclass::dosomething ),
def( "Create", CreateWard ),
def( "Destroy", DestroyWard )
];
const char* src = "print '--- Create' "
"my = Create() "
"print '--- Do something' "
"my:dosomething() "
"print '--- Destroy' "
"Destroy(my) "
"print '--- Do something' "
"my:dosomething()";
if( luaL_dostring( L, src ) )
{
cout << "Error:" << lua_tostring( L, -1 ) << endl;
}
lua_close( L );
}
ward_ptr.hpp
(text/x-c++hdr, 2 KB)
#include <iostream>
#include <ostream>
#include <exception>
template<class T>
class ward_ptr
{
private:
T** ptr;
int *ref_count;
public:
ward_ptr() : ptr(NULL)
{
}
ward_ptr( const ward_ptr<T>& p )
{
std::cout << "Copying ward pointer " << this << " from " << &p << " for raw pointer:" << *(p.ptr) << std::endl;
ptr = p.ptr;
ref_count = p.ref_count;
(*ref_count)++;
}
ward_ptr( T* p )
{
std::cout << "Creating ward pointer " << this << " for raw pointer:" << " " << p << std::endl;
ptr = new T*;
ref_count = new int;
*ptr = p;
*ref_count = 1;
}
T* get() const
{
if( ptr )
std::cout << "ward_ptr:get " << *ptr << std::endl;
if( ! ptr || ! *ptr )
throw std::logic_error( "ward_ptr: exception - instance pointer not set." );
return *ptr;
}
T* operator->() const
{
if( ptr )
std::cout << "ward_ptr:-> " << *ptr << std::endl;
if( ! ptr || ! *ptr )
throw std::logic_error( "ward_ptr: exception - instance pointer not set." );
return *ptr;
}
operator T*() const
{
if( ptr )
std::cout << "ward_ptr: (cast T*) " << *ptr << std::endl;
if( ! ptr || ! *ptr )
throw std::logic_error( "ward_ptr: exception - instance pointer not set." );
return *ptr;
}
void invalidate()
{
std::cout << "Invalidating ward_ptr for raw pointer: " << *ptr << std::endl;
*ptr = NULL;
}
virtual ~ward_ptr()
{
std::cout << "Destroying ward pointer " << this << " for raw pointer:" << *ptr << std::endl;
(*ref_count)--;
if( *ref_count == 0 )
{
delete ref_count;
delete ptr;
}
}
};
template<class T>
std::ostream& operator <<( std::ostream& os, ward_ptr<T> p )
{
return os << p.get();
}
template<class T>
T* get_pointer( ward_ptr<T> p )
{
return p.get();
}