Как стать автором
Обновить

Встречайте Deterministic Garbage Collector Pointer

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


Как вы догадались, если бы у этих примитивов не было бы проблем, то не было бы этой статьи :)

Давайте рассмотри следующий пример классической утечки памяти на 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-объект, то ты возвращаем память, выделенную под наш объект.

Спасибо за внимание!
Теги:
Хабы:
Всего голосов 32: ↑16 и ↓16 0
Просмотры 3.7K
Комментарии Комментарии 22