Ασθενείς αναφορές#

Η Perl παρακολουθεί τη διάρκεια ζωής αντικειμένων με μέτρηση αναφορών. Κάθε τιμή γνωρίζει πόσες αναφορές δείχνουν σε αυτήν· όταν ο μετρητής πέφτει στο μηδέν, η τιμή απελευθερώνεται. Αυτό λειτουργεί καλά για δεδομένα σε σχήμα δέντρου, όπου οι αναφορές ρέουν προς μία κατεύθυνση, από γονέα σε παιδί. Αποτυγχάνει για κύκλους, όπου μια αλυσίδα αναφορών εν τέλει δείχνει πίσω σε κάτι νωρίτερα στην αλυσίδα. Τα μέλη του κύκλου κρατούν το ένα το άλλο ζωντανό ακόμα και όταν τίποτα έξω από τον κύκλο δεν αναφέρεται πλέον σε αυτά.

Μια ασθενής αναφορά είναι αναφορά που δεν αυξάνει τη μέτρηση αναφορών του αναφερόμενου. Σας επιτρέπει να δείχνετε σε κάτι χωρίς να το κρατάτε ζωντανό. Οι ασθενείς αναφορές είναι η πρότυπη απάντηση της Perl σε κύκλους αναφορών.

Πώς διαρρέει ένας κύκλος#

Σκεφτείτε έναν κόμβο διπλά συνδεδεμένης λίστας που κρατάει αναφορές next και prev:

sub new_node {
    my ($value) = @_;
    return { value => $value, next => undef, prev => undef };
}

my $a = new_node('A');
my $b = new_node('B');
$a->{next} = $b;
$b->{prev} = $a;

Τα $a και $b έχουν το καθένα μέτρηση αναφορών 2:

  • Το $a αναφέρεται από τη λεξιλογική $a και από το $b->{prev}.

  • Το $b αναφέρεται από τη λεξιλογική $b και από το $a->{next}.

Όταν οι λεξιλογικές βγαίνουν εκτός εμβέλειας, η συνεισφορά τους πέφτει:

  • Το $a πέφτει σε μέτρηση 1 (απομένει μόνο το $b->{prev}).

  • Το $b πέφτει σε μέτρηση 1 (απομένει μόνο το $a->{next}).

Κανένα από τα δύο δεν φτάνει στο μηδέν. Και οι δύο κόμβοι είναι μη προσπελάσιμοι από οπουδήποτε στο πρόγραμμά σας, ωστόσο κρατούν ο ένας τον άλλο ζωντανό για όλη τη διάρκεια ζωής του διερμηνέα. Αυτή είναι διαρροή.

Διόρθωση της διαρροής με την weaken#

Η Scalar::Util::weaken μετατρέπει μια υπάρχουσα αναφορά σε ασθενή. Η αναφορά συνεχίζει να λειτουργεί για αποαναφορά· απλώς δεν μετράει για τη διατήρηση του στόχου ζωντανού.

Το πρότυπο μοτίβο: όποια κατεύθυνση θεωρείτε «δείκτη προς τα πίσω» ή «δείκτη γονέα», κάντε αυτή ασθενή.

use Scalar::Util qw(weaken);

my $a = new_node('A');
my $b = new_node('B');
$a->{next} = $b;
$b->{prev} = $a;
weaken $b->{prev};             # the back-pointer

Τώρα η μέτρηση αναφορών του $a παραμένει στο 1 (συνεισφέρει μόνο η λεξιλογική $a· το $b->{prev} είναι ασθενές και δεν μετράει). Όταν το $a βγει εκτός εμβέλειας, το $a απελευθερώνεται - και επειδή η απελευθέρωση του $a ρίχνει το $b->{next}, απελευθερώνεται και το $b. Ολόκληρη η δομή καταρρέει.

Όταν μια ασθενής αναφορά γίνεται undef#

Αν ο αναφερόμενος απελευθερωθεί ενώ υπάρχει ακόμα ασθενής αναφορά, η ασθενής αναφορά γίνεται αυτόματα undef. Δεν βλέπετε ποτέ κρεμασμένο δείκτη:

use Scalar::Util qw(weaken);

my $target = { name => 'alive' };
my $weak   = $target;
weaken $weak;

print defined $weak ? "yes\n" : "no\n";   # yes
undef $target;                             # last strong ref gone
print defined $weak ? "yes\n" : "no\n";   # no

Το defined $weak είναι ο πρότυπος τρόπος για να ελέγξετε αν ο στόχος βρίσκεται ακόμα εκεί. Δείτε την defined.

Πού ανήκουν οι ασθενείς αναφορές#

Ο κανόνας: χρησιμοποιήστε ασθενή αναφορά για τον «μη κατέχοντα» δείκτη σε οποιοδήποτε ζεύγος αναφορών που διαφορετικά θα σχημάτιζε κύκλο.

  • Διπλά συνδεδεμένη λίστα. Το next κατέχει· το prev είναι ασθενές.

  • Δέντρο γονέα/παιδιού με δείκτες γονέα. Το children κατέχει· το parent σε κάθε παιδί είναι ασθενές.

  • Μοτίβο παρατηρητή. Το υποκείμενο κρατάει ασθενείς αναφορές στους παρατηρητές του· οι παρατηρητές κρατούν ισχυρές αναφορές στο υποκείμενο. Όταν ένας παρατηρητής αφήσει την αναφορά του, εξαφανίζεται· η εγγραφή του υποκειμένου για αυτόν γίνεται undef και μπορεί να σαρωθεί.

  • Caches που δεν πρέπει να κρατούν τις εγγραφές ζωντανές. Ένα cache του οποίου οι τιμές είναι ασθενείς αναφορές δεν επεκτείνει τη διάρκεια ζωής των αποθηκευμένων αντικειμένων.

isweak - επιθεώρηση μιας αναφοράς#

Η Scalar::Util::isweak σας λέει αν ένα δεδομένο βαθμωτό κρατάει ασθενή αναφορά:

use Scalar::Util qw(weaken isweak);

my $x = { k => 1 };
my $y = $x;
print isweak($y) ? "weak\n" : "strong\n";   # strong
weaken $y;
print isweak($y) ? "weak\n" : "strong\n";   # weak

Χρήσιμη κυρίως όταν αποσφαλματώνετε ένα πρόβλημα διάρκειας ζωής. Ο κώδικας παραγωγής σπάνια διακλαδίζει βάσει της isweak.

Συνηθισμένα λάθη#

  • Ασθενοποίηση του λάθος άκρου. Η ασθενοποίηση του «εμπρόσθιου» δείκτη (αυτού που κατέχει τον διάδοχο) απελευθερώνει τον διάδοχο αμέσως, πριν τον χρησιμοποιήσετε ποτέ. Το ασθενές πάει στον δείκτη προς τα πίσω.

  • Ασθενοποίηση λεξιλογικής. Το weaken $ref όπου η $ref είναι μια φρέσκα λεξιλογική που αναφέρεται στον $target κάνει τη λεξιλογική ασθενή. Αν τίποτα άλλο δεν κρατάει ισχυρή αναφορά στον $target, αυτός εξαφανίζεται στην επόμενη πρόταση και η $ref γίνεται undef. Οι ασθενείς αναφορές ανήκουν σε θυρίδες δομών δεδομένων, όχι τυπικά σε τοπικές λεξιλογικές.

  • Η υπόθεση ότι οι ασθενείς αναφορές είναι αυτόματες. Η Perl δεν εντοπίζει κύκλους και δεν τους ασθενοποιεί για εσάς. Πρέπει να συμμετάσχετε ρητά.

Πέρα από το Scalar::Util#

Το Scalar::Util έρχεται μαζί με την Perl· είναι πάντα διαθέσιμο. Για πιο εξεζητημένες ανάγκες - WeakRef, Hash::Util::FieldHash - καταφύγετε στο CPAN. Για συνηθισμένο κώδικα, η weaken είναι όλο όσο χρειάζεστε.

Πού να πάτε στη συνέχεια#

Ολοκληρώσατε τον οδηγό εκμάθησης αναφορών. Καλά επόμενα αναγνώσματα:

  • Η σελίδα αναφοράς της ref - η πλήρης λίστα των συμβολοσειρών που επιστρέφει, συμπεριλαμβανομένων των περιπτώσεων blessed αντικειμένων.

  • Η bless - πώς μια απλή αναφορά κατακερματισμού γίνεται αντικείμενο μιας συγκεκριμένης κλάσης.

  • Ο οδηγός εκμάθησης αντικειμενοστρέφειας, μόλις είστε έτοιμοι για κλάσεις χτισμένες πάνω στις αναφορές που τώρα κατανοείτε.