Rencontrez le pointeur déterministe du ramasse-miettes

En mémoire, en C ++ il a toujours été difficile de travailler avec lui (un héritage amer de C ) ... Ici C ++ 11 avec son std :: shared_ptr vient à notre aide.


Comme vous l'avez peut-être deviné, si ces primitives n'avaient eu aucun problème, cet article n'aurait pas été :)

Regardons l'exemple suivant d'une fuite de mémoire classique sur 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; } 

Évidemment, nous ne verrons pas l'appel des destructeurs d'objets. Comment y faire face? std :: faiblesse_ptr vient à notre aide:

 ... class Child { ... void setParent(std::shared_ptr<Parent> parentPtr) { parent_ptr_ = parentPtr; } private: std::weak_ptr<Parent> parent_ptr_; }; ... 

Oui, cela aide à résoudre le problème. Mais si vous avez une hiérarchie d'objets plus complexe et qu'il est très difficile de comprendre qui devrait faire std :: faiblesse_ptr et qui devrait être std :: shared_ptr ? Ou ne voulez-vous pas du tout jouer avec des connexions lâches?

Garbage Collector est notre tout !!

Non, bien sûr que non. En C ++, il n'y a pas de support natif pour le garbage collector, et même s'il est ajouté, nous obtenons des frais généraux pour le garbage collector, ainsi que des pauses RAII.

On fait quoi?

Le pointeur déterministe Garbage Collector est un pointeur qui suit tous les liens des objets racine et dès qu'aucun des objets racine ne fait référence à notre objet, il est immédiatement supprimé.

Le principe de son fonctionnement est similaire à std :: shared_ptr (il suit la portée ), mais aussi aux objets qui le référencent.

Regardons le principe de son fonctionnement dans l'exemple précédent:

 #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; } 

Comme vous pouvez le voir, le code est devenu un peu plus, mais c'est le prix à payer pour le retrait entièrement automatique des objets. On peut voir que des méthodes connectToRoot et déconnectFromRoot supplémentaires ont été ajoutées. Bien sûr, écrire avec leurs mains tout le temps sera assez difficile, j'ai donc l'intention de faire un petit générateur de ces méthodes dans les classes qui utilisent gc_ptr (comme nous les voyons, nous adhérons au principe Zero-Overhead , nous ne payons pas pour ce que nous n'utilisons pas, et si nous utilisons- alors les coûts ne sont pas plus élevés que si nous l'écrivions de nos mains).

La bibliothèque gc_ptr.hpp est thread-safe, elle ne crée pas de threads supplémentaires pour le garbage collection, tout est fait dans le constructeur, le destructeur et les opérateurs d'affectation, donc si nous remplaçons notre objet et qu'aucun objet racine ne s'y réfère plus, alors nous retournons mémoire allouée à notre objet.

Merci de votre attention!

Source: https://habr.com/ru/post/fr462583/


All Articles