Search code examples
c++initializationvariable-assignmentallocationcpython

Initialize nontrivial class members when created by PyObject_NEW


I have a Python object referring to a C++ object.

Header:

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <structmember.h>

typedef struct {
    PyObject_HEAD
    FrameBuffer frame_buffer;
} PyFrameBuffer;

extern PyTypeObject PyFrameBuffer_type;

extern PyObject* PyFrameBuffer_NEW(size_t width, size_t height);

The C++ FrameBuffer class is following:

class FrameBuffer {
public:
    explicit FrameBuffer(const size_t width, const size_t height);

    // ...

private:
    size_t m_width;
    size_t m_height;
    std::vector<Pixel> m_pixels;
};

Pixel is a simple class of few plain type elements.

The PyFrameBuffer_NEW() function is following:

PyObject* PyFrameBuffer_NEW(size_t width, size_t height)
{
    auto* fb = (PyFrameBuffer*) PyObject_NEW(PyFrameBuffer, &PyFrameBuffer_type);

    if (fb != nullptr)
    {
        fb->frame_buffer = FrameBuffer(width, height);
    }

    return (PyObject*) fb;
}

This code crashs:

fb->frame_buffer is allocated with undefined values (fb->frame_buffer.m_pixels.size() is undefined).

During fb->frame_buffer assignment, there is a deallocation of an undefinied number of Pixel in m_pixels.

And so, it crash.

This code seems to works:

/// ...
    if (fb != nullptr)
    {
        FrameBuffer fb_tmp(width, height);
        std::memcpy(&fb->frame_buffer, &fb_tmp, sizeof(FrameBuffer));
    }
// ...

But I suspect this is not the proper way to solve this issue (I suspect Pixels in fb_tmp are desallocated outside the scope.

  • What would the the proper way to solve this problem?
  • Is there a commonly known approach on how to expose complex object in CPython?

Solution

  • I've successfully used code similar to

    typedef struct {
        PyObject_HEAD
        FrameBuffer *frame_buffer;
    } PyFrameBuffer;
    

    and then

    if (fb != nullptr)
    {
        fb->frame_buffer = new FrameBuffer( ... );
    }
    

    But you have to account for deleting the object explicitly. You'll need to add a tp_dealloc() function to your PyFrameBuffer initialization that deletes the C++ FrameBuffer object:

    delete fb->frame_buffer;
    

    Also:

    When you use C++ to create a Python extension module, you're linking in your code's C++ runtime libraries. If more than one module uses C++, they all have to use the exact same C++ runtime libraries.