Is it true that whatever is created in tp_alloc should be destroyed in tp_dealloc? And similarly for {tp_new, tp_free}?
It looks like an obvious symmetry, but I would be grateful for clarification.
My actual use case is this: I have:
class OSClass : PyObject {...}
class Final : OSClass {...}
So the corresponding PyTypeObject pto has:
pto->tp_basicsize = sizeof(FinalClass)
pto->tp_dealloc = (destructor) 
                  [](PyObject* pyob) { PyMem_Free(pyob); };
However the new style class stores the PyObject and its corresponding C++ object separately from one another, and therefore works differently.
It creates the PyObject in tp_new, and the corresponding C++ object in tp_init.
And destroys both of them in tp_dealloc
Is this correct/optimal?
Code:
// extra void* to point to corresponding C++ object
pto->tp_basicsize = sizeof(PyObject) + sizeof(void*)
pto->tp_new = new_func;
pto->tp_init = init_func;
pto->tp_dealloc = dealloc_func;
static PyObject* new_func( PyTypeObject* subtype, PyObject* args, PyObject* kwds )
{
    // First we create the Python object.
    // The type-object's tp_basicsize is set to sizeof(Bridge)
    // (Note: We could maybe use PyType_GenericNew for this:
    //   http://stackoverflow.com/questions/573275/python-c-api-object-allocation )
    //
    PyObject* pyob = subtype->tp_alloc(subtype,0);
    Bridge* bridge = reinterpret_cast<Bridge*>(pyob);
    // We construct the C++ object later in init_func (below)
    bridge->m_pycxx_object = nullptr;
    return pyob;
}
static int init_func( PyObject* self, PyObject* args, PyObject* kwds )
{
    try
    {
        Object a = to_tuple(args);
        Object k = to_dict(kwds);
        Bridge* bridge{ reinterpret_cast<Bridge*>(self) };
        // NOTE: observe this is where we invoke the 
        //       constructor, but indirectly (i.e. through final)
        bridge->m_pycxx_object = new FinalClass{ bridge, a, k };
    }
    catch( Exception & )
    {
        return -1;
    }
    return 0;
}
static void dealloc_func( PyObject* pyob )
{
    auto final = static_cast<FinalClass*>( cxxbase_for(pyob) );
    delete final;
    PyMem_Free(pyob);
    COUT( "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" );
    //self->ob_type->tp_free(self);
}
				
                        
From the
tp_newdocumentation you haveThat's why you create the object itself in
tp_newand initialise it intp_init. Creating the C++ object is part of the initialisation. Since thetp_initdocumentation statesYou need to check for
bridge->m_pycxx_object != nullptrand delete the already initialised instance on failure or raise an error.In
tp_deallocyou then destroy the Python object. Since the C++ object is part of this one, it needs to be destroyed there as well.Back to the pairing: You call
tp_allocwithintp_newandtp_freewithintp_dealloc. So {tp_alloc,tp_free} and {tp_new,tp_dealloc} should be considered as pairs.