Память, в С++ с ней было всегда сложно работать (горькое наследство C)… Тут нам на помощь приходит C++11 со своими std::shared_ptr.

Как вы догадались, если бы у этих примитивов не было бы проблем, то не было бы этой статьи :)
Давайте рассмотри следующий пример классической утечки памяти на std::shared_ptr:
Очевидно, что мы не увидим вызов деструкторов объектов. Как с этим бороться? std::weak_ptr приходит нам на помощь:
Да, это помогает решить проблему. Но вот если у вас более сложная иерархия объектов и очень сложно понять, кого следует сделать std::weak_ptr, а кого std::shared_ptr? Или вы не хотите вообще замарачиваться с слабыми связями?
Garbage Collector — наше все !!
Нет, конечно же нет. В С++ нету нативной поддержки Garbage Collector-а, а даже если ее добавят мы получаем накладные рассходы на работу Garbage Collector-а, плюс ломается RAII.
Что же нам делать?
Deterministic Garbage Collector Pointer — это поинтер, который трекает все ссылки от root-объектов и как только никто из root-объектов не ссылается на наш объект, он сразу же удаляется.
Принцип его работы подобен std::shared_ptr (он трекает scope), но также объекты, которые ссылаются на него.
Давайте рассмотрим принцип его работы на предыдущем примере:
Как видим, кода стало немного больше, но это та цена, которую необходимо заплатить за полностью автоматическое удаление объектов. Видно, что добавились дополнительные методы connectToRoot и disconnectFromRoot. Конечно же, писать их руками все время будет довольно сложно, поэтому я намереваюсь сделать небольшой генератор этих методов в классах, которые используют gc_ptr (как видим эти мы придерживаемся принципа Zero-Overhead, мы не платим за то что не используем, а если используем-то расходы не больше, чем если бы мы написали это руками).
Библиотека gc_ptr.hpp потоко-безопасная, она не создает никаких дополнительных потоков для сборки мусора, все выполняется в конструкторе, деструкторе и операторах присваивания, так что если мы затираем наш объект и на него больше не ссылается ни один root-объект, то ты возвращаем память, выделенную под наш объект.
Спасибо за внимание!

Как вы догадались, если бы у этих примитивов не было бы проблем, то не было бы этой статьи :)
Давайте рассмотри следующий пример классической утечки памяти на std::shared_ptr:
#include <iostream> #include <memory> class Child; class Parent { public: Parent() { std::cout << "Parent()" << std::endl; } ~Parent() { std::cout << "~Parent()" << std::endl; } void createChild() { child_ptr_ = std::make_shared<Child>(); } std::shared_ptr<Child> getChild() { return child_ptr_; } private: std::shared_ptr<Child> child_ptr_; }; class Child { public: Child() { std::cout << "Child()" << std::endl; } ~Child() { std::cout << "~Child()" << std::endl; } void setParent(std::shared_ptr<Parent> parentPtr) { parent_ptr_ = parentPtr; } private: std::shared_ptr<Parent> parent_ptr_; }; int main() { auto parent = std::make_shared<Parent>(); parent->createChild(); parent->getChild()->setParent(parent); return 0; }
Очевидно, что мы не увидим вызов деструкторов объектов. Как с этим бороться? std::weak_ptr приходит нам на помощь:
... class Child { ... void setParent(std::shared_ptr<Parent> parentPtr) { parent_ptr_ = parentPtr; } private: std::weak_ptr<Parent> parent_ptr_; }; ...
Да, это помогает решить проблему. Но вот если у вас более сложная иерархия объектов и очень сложно понять, кого следует сделать std::weak_ptr, а кого std::shared_ptr? Или вы не хотите вообще замарачиваться с слабыми связями?
Garbage Collector — наше все !!
Нет, конечно же нет. В С++ нету нативной поддержки Garbage Collector-а, а даже если ее добавят мы получаем накладные рассходы на работу Garbage Collector-а, плюс ломается RAII.
Что же нам делать?
Deterministic Garbage Collector Pointer — это поинтер, который трекает все ссылки от root-объектов и как только никто из root-объектов не ссылается на наш объект, он сразу же удаляется.
Принцип его работы подобен std::shared_ptr (он трекает scope), но также объекты, которые ссылаются на него.
Давайте рассмотрим принцип его работы на предыдущем примере:
#include <iostream> #include "gc_ptr.hpp" class Child; class Parent { public: Parent() { std::cout << "Parent()" << std::endl; } ~Parent() { std::cout << "~Parent()" << std::endl; } void createChild() { child_ptr_.create_object(); } memory::gc_ptr<Child> getChild() { return child_ptr_; } void connectToRoot(void * rootPtr) { child_ptr_.connectToRoot(rootPtr); } void disconnectFromRoot(bool isRoot, void * rootPtr) { child_ptr_.disconnectFromRoot(isRoot, rootPtr); } private: memory::gc_ptr<Child> child_ptr_; }; class Child { public: Child() { std::cout << "Child()" << std::endl; } ~Child() { std::cout << "~Child()" << std::endl; } void setParent(memory::gc_ptr<Parent> parentPtr) { parent_ptr_ = parentPtr; } void connectToRoot(void * rootPtr) { parent_ptr_.connectToRoot(rootPtr); } void disconnectFromRoot(bool isRoot, void * rootPtr) { parent_ptr_.disconnectFromRoot(isRoot, rootPtr); } private: memory::gc_ptr<Parent> parent_ptr_; }; int main() { memory::gc_ptr<Parent> parent; parent.create_object(); parent->createChild(); parent->getChild()->setParent(parent); return 0; }
Как видим, кода стало немного больше, но это та цена, которую необходимо заплатить за полностью автоматическое удаление объектов. Видно, что добавились дополнительные методы connectToRoot и disconnectFromRoot. Конечно же, писать их руками все время будет довольно сложно, поэтому я намереваюсь сделать небольшой генератор этих методов в классах, которые используют gc_ptr (как видим эти мы придерживаемся принципа Zero-Overhead, мы не платим за то что не используем, а если используем-то расходы не больше, чем если бы мы написали это руками).
Библиотека gc_ptr.hpp потоко-безопасная, она не создает никаких дополнительных потоков для сборки мусора, все выполняется в конструкторе, деструкторе и операторах присваивания, так что если мы затираем наш объект и на него больше не ссылается ни один root-объект, то ты возвращаем память, выделенную под наш объект.
Спасибо за внимание!