Можно ли удалить объект, выделенный в python из С++?

В моей программе я управляю ссылками на объекты python в С++. То есть все мои классы получены из класса Referenced, который содержит указатель на соответствующий объект python.

class Referenced
{
public:
    unsigned use_count() const
    { 
        return selfptr->ob_refcnt;
    }

    void add_ref() const
    {
        Py_INCREF(selfptr);
    }

    void remove_ref() const
    {
        Py_DECREF(selfptr);
    }

    PyObject* selfptr;
};

Я использую intrusive_ptr для хранения объектов, полученных из ссылки. Это позволяет мне легко сохранять ссылки на требуемые объекты python на С++ и получать к ним доступ, если это необходимо. Но моя программа вылетает (только в windows howewer), когда объект python будет удален из С++, т.е. когда я вызываю Py_DECREF (selfptr), selfptr- > ob_refcnt == 1. Является ли этот подход ОК?


Обновление: Я, наконец, понял проблему в своей программе. Он не был напрямую связан с удалением объекта. Чтобы проверить исходный вопрос, я внедрил простой модуль расширения, запоминающий ссылку на объект python и освобождающий его по требованию. Вот он:

#include <Python.h>

static PyObject* myObj;

static PyObject* acquirePythonObject(PyObject* self, PyObject* obj)
{
    printf("trying to acquire python object %p, refcount = %d\n", obj, obj->ob_refcnt);
    myObj = obj;
    Py_INCREF(myObj);
    printf("reference acquired\n");
    return Py_True;
}

static PyObject* freePythonObject(PyObject*, PyObject*)
{
    printf("trying to free python object %p, refcount = %d\n", myObj, myObj->ob_refcnt);
    Py_DECREF(myObj);
    printf("reference removed\n");
    return Py_True;
}

static PyMethodDef moduleMethods[] =
{
    {"acquirePythonObject", acquirePythonObject, METH_O, "hold reference to python object."},
    {"freePythonObject", freePythonObject, METH_NOARGS, "free reference to python object."},
    {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC initmodule(void)
{
    Py_InitModule("module", moduleMethods);
}

И python script:

import module

class Foo:
    def __init__(self):
        print "Foo is created"

    def __deinit__(self):
        print "Foo is destroyed"

def acquireFoo():
    foo = Foo()
    module.acquirePythonObject(foo)

def freeFoo():
    module.freePythonObject()

if __name__ == "__main__":
    acquireFoo()
    freeFoo()

Примеры легко запускаются в windows и linux. Ниже представлен результат.

Foo is created
trying to acquire python object 0x7fa19fbefd40, refcount = 2
reference acquired
trying to free python object 0x7fa19fbefd40, refcount = 1
Foo is destoryed
reference removed

Ответ 1

Является ли этот подход ОК?

В принципе, но...

  • Я не вижу никакой гарантии, что add_ref/remove_ref вызывается правильное количество раз (использование RAII автоматизировало бы это - возможно, что делает ваш intrusive_ptr?)
  • Если вы пытаетесь выполнить remove_ref слишком много раз, я не уверен, что гарантирует Python. Если вы установили selfptr = NULL, когда знаете, что пересчет происходит с 1 → 0, вы можете поймать это
    • либо путем сбоя, либо путем проверки явно, либо с помощью Py_XDECREF
    • еще лучше, просто используйте Py_CLEAR вместо

И наконец... есть ли у вас авария или диагностическая информация?