Память, в С++ с ней было всегда сложно работать (горькое наследство 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-объект, то ты возвращаем память, выделенную под наш объект.
Спасибо за внимание!