smart pointers, slicing, and double deletion

Nigel Atkinson <[email protected]>
Newsgroups gmane.comp.lang.lua.bind.user
Message-ID <1259155435.2598.64.camel@finwe>
Smart pointers, slicing, and double deletion.

Sound's like a right mess doesn't it.  I finaly cracked it, and since
I'm sure someone else will come across the same problem I had, I thought
I'd share.

What I was trying to do:
I have a GUI widget (Ogre3d if your wondering), that holds a list of its
children widgets.  This widget class is the base for several Lua widget
classes.  I'm using smart pointers to avoid adoption issues.

Originally using shared pointers, I found that when only C++ had a
pointer to the instance, it would get 'sliced' if it was a Lua class.

Bring in shared_ptr_converter.hpp, and that fixes the slicing.  However
because of part of the way it works, it causes double deletion albeit
seemingly only at the shut down of a Lua thread. (Its not safe across
Lua threads either, but that's easily fixed.)

So to fix the double counting, I started thinking about making my own
shared pointer - oh man, do I have to? - then realised that
boost::intrusive_ptr would do most of the job.  Also rather than store
the counts in the widget class, I just put them in to a map.  Handy for
debugging.  I still might move that data to the widget class, however
this would work if you could not change the class.

So that's the ref counting working now, on to the slicing problem.  I
added code to my intrusive_ptr_add_ref and intrusive_ptr_release
functions, to create and remove, a Lua ref i.e. lua_ref(), lua_unref()
at the appropriate time.  The trick was getting the pointer to the class
representation on to the Lua stack.  After a bit of following the code
line by line as my test program made class instances, I found the bit of
luabind code I needed.

Attached is my test program, and a Lua script to drive it.  I'd be
interested in any comments any of you have about it!

Nigel

------------------------------------------------------------------------------
Let Crystal Reports handle the reporting - Free Crystal Reports 2008 30-Day 
trial. Simplify your report design, integration and deployment - and focus on 
what you do best, core application coding. Discover what's new with
Crystal Reports now.  http://p.sf.net/sfu/bobj-july

_______________________________________________
luabind-user mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/luabind-user
main.cpp (text/x-c++src, 3.6 KB)
#include <iostream>
#include <lua.hpp>
#include <luabind/luabind.hpp>
#include <boost/intrusive_ptr.hpp>
#include <string>
#include <list>
#include <map>

using namespace luabind;
using namespace std;

lua_State *L;

class widget;

int use_count( widget* ptr);

class widget
{
public:
    widget( string caption ) : name(caption), childIter(children.end())
    {
        cout << "widget constructed: " << this << " " << name << endl;
    }
    virtual ~widget()
    {
        while( !children.empty() )
            children.pop_front();

        cout << "widget destructed: " << this << " " << name << endl;
    }
    virtual void show()
    {
        cout << name << endl;
    }
    void run()
    {
        boost::intrusive_ptr<widget> ptr;

        for( reset(), ptr = next(); ptr; ptr = next() )
        {
            ptr->show();
            cout << "use count:" << use_count(ptr.get()) << endl;
        }
    }
    void addChild( boost::intrusive_ptr<widget> w )
    {
        children.push_back(w);
    }
    boost::intrusive_ptr<widget> next()
    {
        if( childIter == children.end() )
            childIter = children.begin();
        else
            childIter++;

        if( childIter != children.end() )
        {
            cout << "next = " << (*childIter)->name << endl;
            return boost::intrusive_ptr<widget>( *childIter );
        }

        cout << "next = nil" << endl;
        return boost::intrusive_ptr<widget>();
    }
    void reset()
    {
        childIter = children.end();
    }
protected:
    string name;
    list<boost::intrusive_ptr<widget> > children;
    list<boost::intrusive_ptr<widget> >::iterator childIter;
};

struct reference
{
	int refCount;
	int luaRef;

	reference() : refCount(0), luaRef(LUA_NOREF)
	{}
};

class widget_wrapper : public widget, public wrap_base
{
    public:
    widget_wrapper( string caption ) : widget( caption )
    {
    }
};

typedef map<widget *, reference> WidgetRefList;

WidgetRefList widgetReferences;

void intrusive_ptr_add_ref( widget* ptr )
{
	reference ref = widgetReferences[ptr];

	ref.refCount++;

	if( ref.luaRef == LUA_NOREF )
	{
		// This object has just been created.  Create a Lua reference to keep
		// it alive in Lua, and the Lua part not garbage collected.
		// This will stop the object getting 'sliced' when the only ptr to it
		// is on the C++ side.
		widget_wrapper* wrapper = dynamic_cast<widget_wrapper*>(ptr);
		assert(wrapper);
		detail::wrap_access::ref(*wrapper).get(L);
		ref.luaRef = lua_ref( L, -1 );
	}
	cout << "refCount for " << ptr << " is now " << ref.refCount << endl;

	widgetReferences[ptr] = ref;
}

void intrusive_ptr_release( widget* ptr )
{
	reference ref = widgetReferences[ptr];

	ref.refCount--;

	cout << "refCount for " << ptr << " is now " << ref.refCount << endl;

	widgetReferences[ptr] = ref;

	if( ref.refCount == 0 )
	{
		lua_unref( L, ref.luaRef );
		widgetReferences.erase( ptr );
		delete ptr;
	}
}

int use_count( widget* ptr )
{
	return widgetReferences[ptr].refCount;
}

int main()
{
    //lua_State *L;
    cout << "Hello from C++" << endl;

    L = lua_open();

    luaL_openlibs( L );

    luabind::open( L );

    module(L)
    [
        class_<
			widget,
			widget_wrapper,
			boost::intrusive_ptr<widget>
			>("widget")
        .def(constructor<string>())
        .def("show", &widget::show )
        .def("run", &widget::run )
        .def("addChild", &widget::addChild )
        .def("next", &widget::next)
        .def("reset", &widget::reset)
    ];

    if( luaL_dofile( L, "test.lua" ) )
    {
        object error( from_stack( L, 1 ) );
        cout << error << endl;
    }

    lua_close(L);

    return 0;
}
test.lua (text/x-lua, 801 B)
print 'Hello from Lua!'

class 'mywidget' (widget)

function mywidget:__init( name )
    widget.__init(self, name)
end

function mywidget:test()
end

function test()

    print '--- Start list ---'

    top:reset()

    c = top:next()
    if c==nil then print( 'got nil' ) end
    while c do
        c:show()
        if c.test then print 'is a mywidget - not sliced!' end
        c = top:next()
        if c==nil then print( 'got nil' ) end
    end

    print '---  End list  ---'
end

top=widget('top')
t={}
for i=1,5 do
    t[i]=mywidget('widget '..i)
    top:addChild(t[i])
    super=nil
end

test()
--print '--- C++ listing ---'
--top:run()
--print '--- end C++ listing ---'

print'Zap t'
t=nil
collectgarbage()

test()
--print '--- C++ listing ---'
--top:run()
--print '--- end C++ listing ---'
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.