Я предполагаю, что OP использует API CPython . ( Мы используем CPython, и части кода выглядят очень похожими / знакомыми.)
Как уже сказано в названии, оно написано на C.
Таким образом, при использовании его для написания привязки Python для классов C ++ разработчик должен знать, что CPython и его API C не & ldquo; знают & rdquo; что-нибудь о C ++. Это должно быть тщательно продумано (похоже на написание связывания C для библиотеки классов C ++).
Когда я пишу классы Python Wrapper, я делаю это всегда с struct
s (чтобы помнить об этом). Можно использовать наследование C ++ в оболочках CPython, чтобы напоминать наследование обернутых классов C ++ (но это единственное исключение из моего вышеприведенного правила).
struct
и class
- это одно и то же в C ++, за исключением (единственного), что по умолчанию все равно public
в struct
, а class
- private
. SO: Класс против Struct только для данных? Кстати. CPython получит доступ к его соотв. переменные-члены компоненты структуры (например, ob_base
) от приведения указателя C (переинтерпретация приведения) и даже не будут распознавать private
-security-попытки.
ИМХО, стоит упомянуть термин POD ( обычные старые данные , также называемый пассивная структура данных ), потому что это то, что делает классы-оболочки C ++ совместим с C. SO: Что такое агрегаты и POD и как / почему они особенные? дает полный обзор этого.
Введение хотя бы одной virtual
функции-члена в класс-оболочку CPython имеет фатальные последствия. Внимательно прочитав приведенную выше ссылку, вы поймете это. Однако я решил проиллюстрировать это небольшим примером кода:
#include <iomanip>
#include <iostream>
// a little experimentation framework:
struct _typeobject { }; // replacement (to keep it simple)
typedef size_t Py_ssize_t; // replacement (to keep it simple)
// copied from object.h of CPython:
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA \
struct _object *_ob_next; \
struct _object *_ob_prev;
// copied from object.h of CPython:
/* Nothing is actually declared to be a PyObject, but every pointer to
* a Python object can be cast to a PyObject*. This is inheritance built
* by hand. Similarly every pointer to a variable-size Python object can,
* in addition, be cast to PyVarObject*.
*/
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;
/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD PyObject ob_base;
void dump(std::ostream &out, const char *p, size_t size)
{
const size_t n = 16;
for (size_t i = 0; i < size; ++p) {
if (i % n == 0) {
out << std::hex << std::setw(2 * sizeof p) << std::setfill('0')
<< (size_t)p << ": ";
}
out << ' '
<< std::hex << std::setw(2) << std::setfill('0')
<< (unsigned)*(unsigned char*)p;
if (++i % n == 0) out << '\n';
}
if (size % n != 0) out << '\n';
}
// the experiment:
static PyObject pyObj;
// This is correct:
struct Wrapper1 {
PyObject_HEAD
int myExt;
};
static Wrapper1 wrap1;
// This is possible:
struct Wrapper1Derived: Wrapper1 {
double myExtD;
};
static Wrapper1Derived wrap1D;
// This is effectively not different from struct Wrapper1
// but things are private in Wrapper2
// ...and Python will just ignore this (using C pointer casts).
class Wrapper2 {
PyObject_HEAD
int myExt;
};
static Wrapper2 wrap2;
// This is FATAL - introduces a virtual method table.
class Wrapper3 {
private:
PyObject_HEAD
int myExt;
public:
Wrapper3(int value): myExt(value) { }
virtual ~Wrapper3() { myExt = 0; }
};
static Wrapper3 wrap3{123};
int main()
{
std::cout << "Dump of PyObject pyObj:\n";
dump(std::cout, (const char*)&pyObj, sizeof pyObj);
std::cout << "Dump of Wrapper1 wrap1:\n";
dump(std::cout, (const char*)&wrap1, sizeof wrap1);
std::cout << "Dump of Wrapper1Derived wrap1D:\n";
dump(std::cout, (const char*)&wrap1D, sizeof wrap1D);
std::cout << "Dump of Wrapper2 wrap2:\n";
dump(std::cout, (const char*)&wrap2, sizeof wrap2);
std::cout << "Dump of Wrapper3 wrap3:\n";
dump(std::cout, (const char*)&wrap3, sizeof wrap3);
return 0;
}
Скомпилировано и запущено:
Dump of PyObject pyObj:
0000000000601640: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000000000601650: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Dump of Wrapper1 wrap1:
0000000000601600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000000000601610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000000000601620: 00 00 00 00 00 00 00 00
Dump of Wrapper1Derived wrap1D:
00000000006015c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000000006015d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000000006015e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Dump of Wrapper2 wrap2:
0000000000601580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000000000601590: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000000006015a0: 00 00 00 00 00 00 00 00
Dump of Wrapper3 wrap3:
0000000000601540: d8 0e 40 00 00 00 00 00 00 00 00 00 00 00 00 00
0000000000601550: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0000000000601560: 00 00 00 00 00 00 00 00 7b 00 00 00 00 00 00 00
Демонстрация в реальном времени на coliru
Дампы pyObj
, wrap1
, wrap1D
, wrap2
состоят только из 00
s & ndash; неудивительно, я сделал их static
. wrap3
выглядит немного по-другому, частично из-за конструктора (7b
== 123) и частично из-за того, что компилятор C ++ поместил понтер VMT в экземпляр класса, к которому очень вероятно принадлежит d8 0e 40
. (Я предполагаю, что указатель VMT имеет размер любого указателя на функцию, но на самом деле я не знаю, как компилятор организует вещи внутренне.)
Представьте себе, что происходит, когда CPython берет адрес wrap3
, преобразует его в PyObject*
и записывает указатель _ob_next
со смещением 0, который используется для объединения объектов Python в двойной список. (Надеюсь, что это сбой или что-то еще, что еще больше ухудшит ситуацию.)
Представьте, в свою очередь, что происходит в функции создания OP
static PyObject *B_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
B *self = (B*)type->tp_alloc(type, 0);
new (self)B;
return (PyObject*)self;
}
, когда конструктор размещения B
переопределяет инициализацию внутренних элементов PyObject
, что, вероятно, произошло в tp_alloc()
.