D'après ce que je peux voir, il existe deux formes omniprésentes de gestion des ressources: la destruction déterministe et explicite. Des exemples des premiers seraient des destructeurs C ++ et des pointeurs intelligents ou le sous-marin DESTROY de Perl, tandis qu'un exemple du second serait le paradigme des blocs à gérer des ressources de Ruby ou l'interface IDispose de .NET.
Les langues plus récentes semblent opter pour cette dernière, peut-être comme effet secondaire de l'utilisation de systèmes de collecte des ordures de la variété sans comptage de références.
Ma question est la suivante: étant donné que les destructeurs de pointeurs intelligents ou de systèmes de collecte des ordures comptant les références - presque la même chose - permettent la destruction implicite et transparente des ressources, est-ce une abstraction moins fuyante que les types non déterministes qui s'appuient sur des explicites notation?
Je vais donner un exemple concret. Si vous avez trois sous-classes C ++ d'une même superclasse, l'une peut avoir une implémentation qui ne nécessite aucune destruction spécifique. Peut-être que sa magie opère d'une autre manière. Le fait qu'il n'ait pas besoin de destruction spéciale n'est pas pertinent - toutes les sous-classes sont toujours utilisées de la même manière.
Un autre exemple utilise des blocs Ruby. Deux sous-classes doivent libérer des ressources, donc la superclasse opte pour une interface qui utilise un bloc dans le constructeur, même si d'autres sous-classes spécifiques peuvent ne pas en avoir besoin car elles ne nécessitent aucune destruction spéciale.
Est-il vrai que ce dernier divulgue les détails de mise en œuvre de la destruction des ressources, alors que le premier ne le fait pas?
EDIT: Comparer, disons, Ruby à Perl pourrait être plus juste car l'un a une destruction déterministe et l'autre pas, mais ils sont tous deux récupérés.
la source
(*ptr).Message()
ou de manière équivalenteptr->Message()
. Il existe un ensemble infini d'expressions autorisées, comme((*ptr))->Message
c'est également équivalent. Mais ils se résument tous àexpressionIdentifyingAnObject.Message()
Réponses:
Votre propre exemple répond à la question. La destruction transparente est nettement moins étanche que la destruction explicite. Il peut fuir, mais l'est moins.
La destruction explicite est analogue à malloc / free en C avec tous les pièges. Peut-être avec du sucre syntaxique pour le faire apparaître basé sur la portée.
Certains des avantages de la destruction transparente par rapport à l'explicite: -
même modèle d'utilisation
- vous ne pouvez pas oublier de libérer la ressource.
- les détails de nettoyage ne jonchent pas le paysage au point d'utilisation.
la source
L'échec dans l'abstraction n'est en fait pas le fait que la collecte des ordures n'est pas déterministe, mais plutôt dans l'idée que les objets sont "intéressés" par les choses auxquelles ils renvoient et ne sont pas intéressés par les choses auxquelles ils ne tiennent pas. références. Pour voir pourquoi, considérons le scénario d'un objet qui maintient un compteur de la fréquence à laquelle un contrôle particulier est peint. Lors de la création, il souscrit à l'événement "paint" du contrôle, et lors de l'élimination, il se désabonne. L'événement click incrémente simplement un champ et une méthode
getTotalClicks()
renvoie la valeur de ce champ.Lorsque l'objet compteur est créé, il doit provoquer le stockage d'une référence à lui-même dans le contrôle qu'il surveille. Le contrôle ne se soucie vraiment pas du contre-objet et serait tout aussi heureux si le contre-objet et la référence à celui-ci cessaient d'exister, mais tant que la référence existe, il appellera le gestionnaire d'événements de cet objet à chaque fois. il se peint. Cette action est totalement inutile au contrôle, mais serait utile à quiconque invoquerait jamais
getTotalClicks()
l'objet.Si, par exemple, une méthode devait créer un nouvel objet "compteur de peinture", effectuer une action sur le contrôle, observer combien de fois le contrôle a été repeint, puis abandonner l'objet compteur de peinture, l'objet resterait abonné à l'événement même bien que personne ne s'en soucierait jamais si l'objet et toutes les références à lui disparaissaient simplement. Les objets ne deviendraient cependant pas éligibles à la collecte avant le contrôle lui-même. Si la méthode était invoquée plusieurs milliers de fois au cours de la durée de vie du contrôle [un scénario plausible], elle pourrait provoquer un débordement de mémoire, mais pour le fait que le coût de N invocations serait probablement O (N ^ 2) ou O (N ^ 3) à moins que le traitement des abonnements ne soit très efficace et que la plupart des opérations n'impliquent en fait aucune peinture.
Ce scénario particulier pourrait être géré en donnant au contrôle de conserver une référence faible au contre-objet plutôt qu'une référence forte. Un modèle d'abonnement faible est utile, mais ne fonctionne pas dans le cas général. Supposons qu'au lieu de vouloir avoir un objet qui surveille un seul type d'événement à partir d'un seul contrôle, on voulait avoir un objet enregistreur d'événements qui surveillait plusieurs contrôles, et le mécanisme de gestion des événements du système était tel que chaque contrôle avait besoin d'une référence à un autre objet du journal des événements. Dans ce cas, l'objet reliant un contrôle à l'enregistreur d'événements ne doit rester actif que tant que les deuxle contrôle surveillé et le journal des événements restent utiles. Si ni le contrôle ni le journal des événements ne contiennent une référence forte à l'événement de liaison, il cessera d'exister même s'il est toujours "utile". Si l'un ou l'autre détient un événement fort, la durée de vie de l'objet de liaison peut être inutilement prolongée même si l'autre meurt.
Si aucune référence à un objet n'existe nulle part dans l'univers, l'objet peut en toute sécurité être considéré comme inutile et éliminé de l'existence. Le fait qu'une référence existe à un objet, cependant, n'implique pas que l'objet est "utile". Dans de nombreux cas, l'utilité réelle des objets dépendra de l'existence de références à d' autres objets qui, du point de vue du GC, ne leur sont absolument pas liés.
Si les objets sont notifiés de manière déterministe lorsque personne ne les intéresse, ils pourront utiliser ces informations pour s'assurer que quiconque bénéficierait de ces connaissances en sera informé. En l'absence d'une telle notification, cependant, il n'existe aucun moyen général de déterminer quels objets sont considérés comme "utiles" si l'on ne connaît que l'ensemble des références qui existent, et non le sens sémantique attaché à ces références. Ainsi, tout modèle qui suppose que l'existence ou l'inexistence de références est suffisante pour une gestion automatisée des ressources serait voué à l'échec même si le GC pouvait détecter instantanément l'abandon d'objets.
la source
Aucun "destructeur, ou autre interface qui dit" cette classe doit être détruite "n'est un contrat de cette interface. Si vous créez un sous-type qui ne nécessite pas de destruction spéciale, je serais enclin à considérer qu'une violation du principe de substitution de Liskov .
Quant à C ++ par rapport aux autres, il n'y a pas beaucoup de différence. C ++ force cette interface sur tous ses objets. Les abstractions ne peuvent pas fuir lorsqu'elles sont requises par la langue.
la source
DerivedFooThatRequiresSpecialDestruction
ne peut être créé que par du code qui appellenew DerivedFooThatRequiresSpecialDestruction()
. D'un autre côté, une méthode d'usine qui retourne unDerivedFooThatRequiresSpecialDestruction
code qui ne s'attendait pas à quelque chose nécessitant une destruction serait une violation LSP.Devoir surveiller les cycles à la main n'est ni implicite ni transparent. La seule exception est un système de comptage de référence avec un langage qui interdit les cycles par conception. Erlang pourrait être un exemple d'un tel système.
Donc, les deux approches fuient. La principale différence est que les destructeurs fuient partout en C ++ mais
IDispose
sont très rares sur .NET.la source