Комментарии 25
Но, мне кажется в конце концов все равно вас не спасут разные типы ссылок, и вам прийдется создават сборщик мусора.
Вопрос — глядя на все эти проблемы, и кучу доп софта для решения этих проблем, потратив столько же ресурсов и времени смогли бы написать сборщик мусора?
ЗЫ: У меня был проект на дельфи orm. Мне очень понятно с чем вы столкнулись. После первых проблем сразу написали сборщик мусора и в дальнейшем это показало что это было самое правильное решение.
Одна из принципиальных проблем с использованием сборщиков мусора в нашем случае — в том, что мы поставляем библиотеки, а не приложения. Произвольный клиентский код будет не слишком рад, если его вдруг начнут собирать.
Разве не получается при таком подходе, что из всего C# вам можно использовать подмножество C# 2 (недавно вышел C# 9)? Лямбды, LINQ, Span и так далее за бортом. Причём штуки вроде Span могли бы серьезно ускорить вашу библиотеку за счёт снижения давления на GC.
Как вы проверяете, что C++ версия не течёт? Задача выглядит нерешаемой… В C# есть гарантии со стороны рантайма и системы типов, после перевода в C++ все они превращаются в тыкву. Надеяться, что программисты на C# правильно расставят аннотации и что они будут «правильно» пользоваться объектами — разве это работает на практике?
Я не понимаю почему mono AOT для вас не работает. Но если есть какие-то причины, то можно поместить объектную модель в неуправляемую кучу, так чтобы C++ мог легко с ней работать. Модифицировать эту модель через методы реализованные на C#. Тогда вы не платите за маршалинг данных между C++/C#
Лямбды транслируются в лямбды же, продление времени жизни переменных мы сейчас прикручиваем на этапе кодогенерации. LINQ транслируется, получается функционально эквивалентный код. Span в наших продуктах не использовался, но, если будет, я не вижу с первого взгляда особых проблем с тем, чтобы эмулировать его через aligned_storage. Из того, что мы пока не прожевали, на ум приходят yeild (хотя на уровне кодогенерации это можно сделать, просто не было настолько нужно) и виртуальные обобщённые функции (т. к. дженерики мы транслируем в шаблоны, виртуальность под запретом).
Проверка утечек на C++ делается циклическим запуском тестов через --gtest_repeat, API, не покрытого тестами, у нас нет, хотя могут оставаться отдельные ветки. Аннотации против утечек расставляют программисты C++ при подготвоке очередного релиза, они же проверяют потребление памяти.
По поводу использования mono — в прошлой статье я писал о том, что C++-версия делалась, в основном, по анналогии с Java-версией, для которой данный подход сработал примерно в то время, когда mono ещё был достаточно сырым фреймворком. Кроме того, непосредственный проброс API не избавит от необходимости решать задачи согласования с плюсовым кодом (обёртки над System.IO.Stream для использования с iostream, итераторы для IEnumerable, синтаксический сахар для работы со строками, и т. д.). Хотя, разумеется, в чём-то это сократило бы объём работы, но зато сделало бы использование продукта более сложным для клиентов.
Лямбды транслируются в лямбды же, продление времени жизни переменных мы сейчас прикручиваем на этапе кодогенерации.
То есть вы анализируете текст лямбды чтобы понять какие переменные она захватывает? Кажется тут могут возникать неоднозначные ситуации связанные с владением. А атрибут вам поставить некуда.
Если вы имеете дело с unsafe в C#, взаимодействуете с нативным кодом через Interop или вообще пишете нативные приложения на том же C++, то да, там могут возникнуть проблемы с циклическими ссылками.
Возможно, я что-то упустил, но в названии статьи вставлено такое красивое слово "Модель памяти C++", но не вижу ничего про портирование синхронизации работы потоков, а ведь это большой кусок собственно модели памяти C++.
P.S. Про модель работы синхронизации в C# знаю мало, поэтому интересно посмотреть что там получается
Не до конца понятно чем не подошел std::make_shared. Он как раз и предназначен для локализации памяти control block с памятью объекта. Ну и проблемы его тоже известны — пока хоть одна слабая ссылка будет удерживаться, вся память под объект и control block не вернется в систему.
#include <iostream>
#include <memory>
using namespace std;
class Object : public enable_shared_from_this<Object>
{
public:
Object()
{}
virtual ~Object()
{}
};
class Node;
class Document : virtual public Object
{
shared_ptr<Node> m_root;
public:
virtual ~Document()
{}
Document()
{
m_root = make_shared<Node>(dynamic_pointer_cast<Document>(shared_from_this()));
}
};
class Node : virtual public Object
{
weak_ptr<Document> m_document;
public:
virtual ~Node()
{}
Node(shared_ptr<Document> document)
{
m_document = document;
}
};
int main(int, char*[])
{
auto doc = make_shared<Document>();
return 0;
}
В данном конкретном случае все решается ленивыми свойствами.
class Document : virtual public Object
{
shared_ptr<Node> m_root;
public:
virtual ~Document()
{}
Document()
{
m_root = make_shared<Node>(dynamic_pointer_cast<Document>(shared_from_this()));
}
shared_ptr<Node> GetRoot()
{
if (!m_root)
{
m_root = make_shared<Node>(dynamic_pointer_cast<Document>(shared_from_this()));
}
return m_root;
}
};
Но, понятно, что это не панацея.
Как мы заставили код, портированный с C#, работать с моделью памяти C++