Как управлять памятью в интеграции Embeddable Common LISP и с++?
Пробую создать приложение c++, которое умеет "выполнять" lisp код. Для интеграции использую Embeddable Common LISP
Абстрактный пример
код на лисп:
(setq my_class (create-myclass))
будет вызывать код в c++:
cl_object get_my_class()
{
MyClass* myClass = new MyClass;
cl_object cl_myClass = ecl_make_foreign_data(ecl_make_simple_base_string("*", 1), 0, myClass);
return cl_myClass;
}
Переменная LISP - my_class в c++ будет соответствовать указателю на MyClass*
Но есть проблема.
(setq my_class nil)
(gc)
Этот lisp код не освобождает память указателю на MyClass* в c++.
Для решения проблемы можно создать лисп функцию
(delete-myclass my_class)
Она будет соответствовать вот такому коду в c++:
cl_object del_my_class(cl_object my_class)
{
auto myClass_fptr = ecl_foreign_data_pointer_safe(my_class);
MyClass* myClass = static_cast<MyClass*>(myClass_fptr);
delete myClass;
return Cnil;
}
Но тут возникает проблема. В отладчике cl_object my_class и MyClass* myClass имеют один и тот же адрес.
Это значит delete myClass; освобождает и cl_object my_class. То-есть вместе с ними переменная в лисп получает неопределённое значение.
Возникает вопрос: delete myClass
в c++ удалит данные на которые указывает my_class
в лиспе. Когда в лиспе закончится жизненный цикл переменной my_class
, не произойдет ли Undefined Behavior или Access Violation когда с ней будет работать GC лиспа? Потому-что my_class
в лиспе указывает на удаленные данные.
Нигде не нашел информации по этой теме.
В Debug режиме c++ ошибки подобного рода могут и не возникать. А вот в релизной версии мне кажется GC лиспа может совершить Access Violation если данные lisp переменной были удалены или совершить double delete.
Вот этот вариант я не хочу использовать:
Есть еще метод использовать не указатели, а дескрипторы или хендлеры. Это значит поместить классы в контейнер и обращаться к ним через ключи этого контейнера.
cl_object create_myclass()
{
MyClass* myClass = new MyClass;
// add MyClass* in container and return int class_id
int class_id = Container->add(myClass);
return ecl_make_int(class_id);
}
и тогда
(setq my_class (create-myclass))
сохранит в переменной my_class число при помощи которого можно будет обращаться к контейнеру класса:
(myclass-set-value my_class "new value")
В c++ будет работать примерно так:
cl_object myclass_set_value(cl_object my_class, cl_object new_value)
{
int class_id = ecl_to_int(my_class);
// get MyClass* from the container by class_id
MyClass* myClass = Container->get(class_id);
string str = ecl_to_string(new_value);
myClass->setValue(str);
return Cnil;
}
Тут мы полностью избавляемся от проблем управления памятью. Но я бы не хотел использовать этот способ. Работать с указателями на объекты намного проще, чем создавать прокси-контейнеры или фабрики.
Хотелось бы четко понимать как работает управление памятью при интеграции работы с указателями.