2010-12-08 62 views
4

Je me demande quand une instance d'une classe dérivée de TInterfacedObject est détruite et qui appelle le destructeur. J'ai écrit une classe ScopedLock, qui devrait automatiquement appeler la méthode Release d'un objet de synchronisation lorsque l'instance sort de la portée. C'est un concept RAII connu en C++, mais je ne sais pas s'il est garanti que le destructeur est appelé, quand l'instance de verrouillage sort de sa portée.Quand TInterfacedObject.Destroy est appelé (une classe ScopedLock)

ILock = interface 
end; 

ScopedLock<T: TSynchroObject> = class(TInterfacedObject, ILock) 
strict private 
    sync_ : T; 
public 
    constructor Create(synchro : T); reintroduce; 
    destructor Destroy;override; 
end; 

implementation 
{ ScopedLock<T> } 

constructor ScopedLock<T>.Create(synchro: T); 
begin 
    inherited Create;; 
    sync_ := synchro; 
    sync_.Acquire; 
end; 

destructor ScopedLock<T>.Destroy; 
begin 
    sync_.Release; 
    inherited; 
end; 

{ Example } 
function Example.Foo: Integer; 
var 
    lock : ILock; 
begin 
    lock := ScopedLock<TCriticalSection>.Create(mySync); 
    // ... 
end; // mySync released ? 

Cela fonctionne très bien dans un cas de test simple, mais est-ce sécuritaire?

Répondre

5

Oui, c'est économiser. Votre code

function Example.Foo: Integer; 
var 
    lock : ILock; 
begin 
    lock := ScopedLock<TCriticalSection>.Create(mySync); 
    // ... 
end; 

est compilé comme le pseudo-code suivant

function Example.Foo: Integer; 
var 
    lock : ILock; 
begin 
    lock := ScopedLock<TCriticalSection>.Create(mySync); 
    lock._AddRef; // ref count = 1 
    try 
// .. 
    finally 
    lock._Release; // ref count = 0, free lock object 
    end; 

Vous pouvez voir que lorsque verrouillage var est hors de portée son compte ref est décrémenté, est devenu nul et objet de verrouillage est automatiquement détruit.

6

Le seul problème est si la fonction est en ligne: la variable locale contenant la référence ILock sera promue à la portée de la fonction appelant la fonction inline. Cela pourrait faire que le verrou vive plus longtemps que vous le vouliez. D'autre part, il n'est pas nécessaire de déclarer une variable pour contenir la référence d'interface si vous écrivez une fonction (par exemple une fonction de classe appelée Create) qui renvoie une référence d'interface (par opposition à une référence d'objet). Le compilateur créera un local caché pour recevoir la valeur de retour (car tous les types gérés comme les interfaces et les chaînes sont retournés en passant une variable de résultat). Ce local caché agira comme le local explicite.

j'ai écrit plus ici: http://blog.barrkel.com/2010/01/one-liner-raii-in-delphi.html

+1

Bon article .. Pouvez-vous me dire comment le compilateur Delphi décide d'intégrer une fonction ou non? Existe-t-il des méthodes pour empêcher/appliquer inline? – hansmaad

+0

@hansmaad: Si {$ INLINE AUTO} n'est pas activé, il n'introduira jamais automatiquement une fonction non marquée avec la directive inline. Mais $ INLINE AUTO n'utilise pas une heuristique très intelligente - elle va intégrer toutes les fonctions en dessous d'une certaine taille, et peut facilement conduire à une explosion de taille. Cela signifie qu'il est peu probable que $ INLINE AUTO soit en vigueur. Ainsi, vous n'avez besoin de vous en soucier que si vous marquez vous-même la fonction de ligne avec la directive 'inline' à la fin de l'en-tête de la procédure. –

0

Je ne vois pas que l'approche que vous épousez, tout juste, est en fait mieux que le bon vieux try/finally. Vous avez pris une solution claire et explicite et l'avez remplacée par une solution obscure et opaque.

Le vrai travail Le code Delphi est plein de Try/Finally et donc ça devrait être un idiome naturel. Je ne vois pas l'inconvénient de l'écriture:

mySync.Acquire; 
Try 
    //do stuff 
Finally 
    mySync.Release; 
End; 
+0

Parce que c'est moins de code? Ou, parce que cela rend la portée explicitement évidente au moment où elle est créée. Il y a probablement d'autres raisons aussi. Essayez/finalement n'est pas mauvais, mais d'autres techniques peuvent être bonnes aussi vous le savez :) –

+0

@David M étant donné que vous devez être habitué à lire et à analyser Try/Enfin, vous pouvez aussi bien l'utiliser partout où il fait le travail. Avoir plusieurs façons de protéger une ressource (fuite ou sérialisation), à mon avis, crée des complications supplémentaires lors de la lecture et de la vérification du code. Je suppose que je suis de l'école Python de "one way to do things" par opposition à l'école Perl de "many ways" !! –

+4

@Daivd H: Pour moi, la "seule façon de faire les choses" est d'utiliser les techniques RAII quand cela est possible.Je ne peux pas me tromper avec ça. Chaque fois que j'achète une sérialisation, je suis sûr que je vais obtenir la sortie au bon endroit, sans écrire des lignes supplémentaires de code que je vais oublier tôt ou tard. – hansmaad