defer#
Προγραμματισμός εκτέλεσης ενός μπλοκ όταν εξέρχεται η περικλείουσα εμβέλεια, για οποιονδήποτε λόγο.
Η defer είναι δεσμευμένη λέξη ελέγχου ροής, όχι συνάρτηση. Γράφοντας defer { ... } μέσα σε μια εμβέλεια καταγράφει το μπλοκ για μεταγενέστερη εκτέλεση· όταν ο έλεγχος εγκαταλείπει το περικλείον μπλοκ - με πτώση από το τέλος, με return, με die, με goto, ή με μια δήλωση ελέγχου βρόχου (next, last, redo) - το αποθηκευμένο μπλοκ εκτελείται κατά την έξοδο. Είναι το τυπικό ιδίωμα για τη συσχέτιση απόκτησης με απελευθέρωση (open/close, lock/unlock, push/pop) χωρίς εξάρτηση από καταστροφείς αντικειμένων ή τύλιγμα κάθε διαδρομής σε eval.
Το χαρακτηριστικό είναι πειραματικό και πρέπει να ενεργοποιηθεί. Είτε δηλώστε ρητή συμμετοχή είτε χρησιμοποιήστε ένα bundle:
use feature 'defer'; # explicit
use v5.36; # bundle - enables defer among others
Υπό use warnings 'experimental::defer' (ή απλό use warnings) η πρώτη χρήση εκπέμπει πειραματική προειδοποίηση. Σιγάστε την με no warnings 'experimental::defer' αφότου αποφασίσετε να βασιστείτε στο χαρακτηριστικό.
Σύνοψη#
defer BLOCK
Η defer δέχεται ένα μπλοκ οριοθετημένο από αγκύλες και τίποτε άλλο. Δεν υπάρχει μορφή έκφρασης, μορφή με filehandle, ούτε τιμή επιστροφής - η δήλωση υπάρχει για την παρενέργειά της κατά την έξοδο από την εμβέλεια.
Τι επιστρέφεται#
Η defer είναι δήλωση, όχι έκφραση. Δεν συνεισφέρει καμία τιμή στο περιβάλλον της. Η ίδια η τιμή επιστροφής του μπλοκ απορρίπτεται όταν εκτελείται κατά την έξοδο από την εμβέλεια.
Πότε εκτελείται το αναβληθέν μπλοκ#
Το αναβληθέν μπλοκ εκτελείται μία φορά, όταν ο έλεγχος εγκαταλείπει το άμεσα περικλείον μπλοκ, ανεξάρτητα από το πώς εξέρχεται ο έλεγχος:
κανονική πτώση από το τέλος του μπλοκ,
ρητή
returnαπό την περιέχουσα υπορουτίνα,εξαίρεση που εκτοξεύτηκε από
dieή διαδόθηκε από κληθείσα υπορουτίνα,gotoεκτός του μπλοκ,
Αν η ροή εκτέλεσης δεν φτάσει ποτέ στην ίδια τη δήλωση defer, το μπλοκ δεν εισάγεται στην ουρά και δεν εκτελείται. Αυτό είναι το ακριβές αντίθετο ενός φασικού END, τον οποίο ο μεταγλωττιστής εισάγει στην ουρά άνευ όρων:
use feature 'defer';
{
defer { say "This will run"; }
return;
defer { say "This will not"; } # never reached, never scheduled
}
Σειρά LIFO#
Πολλαπλά μπλοκ defer στην ίδια εμβέλεια εκτελούνται με σειρά last-in, first-out - το πιο πρόσφατα προγραμματισμένο μπλοκ εκτελείται πρώτο. Αυτό ταιριάζει στη φυσική εμφώλευση της απόκτησης πόρων: το τελευταίο που αποκτήθηκε είναι το πρώτο που απελευθερώνεται.
use feature 'defer';
{
defer { say "1"; }
defer { say "2"; }
defer { say "3"; }
}
# prints 3, then 2, then 1
Καθολική κατάσταση που επηρεάζει#
Καμία άμεσα. Το αναβληθέν μπλοκ βλέπει τις καθολικές του διερμηνέα ($_, $!, $@, το επιλεγμένο filehandle, κ.λπ.) με όποιες τιμές κρατούν τη στιγμή που εκτελείται το μπλοκ, όχι τη στιγμή που προγραμματίστηκε. Μια defer που θέλει να διατηρήσει την $! ή την $@ κατά τον καθαρισμό πρέπει να τις αποθηκεύσει και να τις αποκαταστήσει η ίδια - αλλιώς μια κλήση συστήματος μέσα στο μπλοκ θα τις καταστρέψει και ο καλών της περικλείουσας υπορουτίνας θα δει τις αντικατεστημένες τιμές.
defer {
local ($!, $@);
close $fh; # clobbers $! on failure - local'd copy absorbs it
}
Παραδείγματα#
Συσχετισμός μιας open με την αντίστοιχη close, με εγγύηση εκτέλεσης ακόμα και σε die:
use feature 'defer';
sub read_config {
open my $fh, "<", "config.ini" or die "open: $!";
defer { close $fh; }
while (<$fh>) { ... }
# close $fh runs here on fallthrough, on return, and on die.
}
Ξεκλείδωμα ενός κοινόχρηστου πόρου σε κάθε διαδρομή εξόδου:
use feature 'defer';
sub critical_section {
$lock->acquire;
defer { $lock->release; }
do_work(); # release runs on fallthrough
return if $shortcut; # release runs before the return completes
die "boom" if $bad; # release runs, then the exception propagates
}
Καθαρισμός LIFO εμφωλευμένων πόρων - ο εξωτερικός πόρος απελευθερώνεται τελευταίος, κάτι που συνήθως είναι το επιθυμητό:
use feature 'defer';
sub copy_file {
open my $in, "<", $src or die "open $src: $!";
defer { close $in; }
open my $out, ">", $dst or die "open $dst: $!";
defer { close $out; }
print {$out} $_ while <$in>;
# close $out runs first, then close $in.
}
Προσωρινή προσαρμογή της κατάστασης του διερμηνέα και αποκατάστασή της κατά την έξοδο χωρίς χρήση του local:
use feature 'defer';
sub with_separator {
my $saved = $,;
$, = " | ";
defer { $, = $saved; }
print @_, "\n";
}
Οριακές περιπτώσεις#
Υπό συνθήκη προγραμματισμός. Μια δήλωση
deferπρογραμματίζει το μπλοκ της μόνο όταν ο έλεγχος πραγματικά φτάσει σε αυτήν. Μια πρόωρηreturnήdieπου συμβαίνει πριν τη γραμμήdeferδεν αφήνει τίποτα προς εκτέλεση. Τοποθετήστε τηνdeferαμέσως μετά τη συσχετισμένη απόκτηση, ώστε να μην μπορούν να διαχωριστούν από μια ενδιάμεση αποτυχία.Αφού προγραμματιστεί, εκτελείται πάντα. Δεν υπάρχει μηχανισμός ακύρωσης του προγραμματισμού ενός μπλοκ
defer. Αν η απόκτηση που απελευθερώνει το μπλοκ έχει αποτύχει, είτε μην προγραμματίζετε τηνdefer(κρατήστε την μετά τον έλεγχο επιτυχίας) είτε κάντε το μπλοκ ταυτοδυναμικό ώστε μια περιττή απελευθέρωση να είναι αβλαβής.Καμία διαφυγή ροής ελέγχου από το μπλοκ. Ένα μπλοκ
deferμπορεί να εκτοξεύσει εξαίρεση, αλλά δεν μπορεί να κάνειreturnαπό την περικλείουσα υπορουτίνα, να κάνειgotoσε ετικέτα εκτός του εαυτού του, ή να εκτελέσειnext,lastήredoγια βρόχο που περικλείει τηνdefer. Αυτές οι κατασκευές είναι αποδεκτές μέσα στηνdeferόταν δρουν σε βρόχους που περιέχονται εξ ολοκλήρου εντός του ίδιου του σώματος του μπλοκ:defer { foreach (1 .. 5) { last if $_ == 3; } # permitted } foreach (6 .. 10) { defer { last if $_ == 8; } # forbidden }
Εξαίρεση κατά τη διάρκεια ξετυλίγματος εξαίρεσης. Αν το μπλοκ
deferεκτελείται επειδή διαδίδεται μια εξαίρεση και το ίδιο το μπλοκ εκτοξεύει, η γλώσσα δεν καθορίζει την προκύπτουσα εξαίρεση - μόνο ότι ο καλών θα δει κάποια. Μη βασίζεστε σε ποια εξαίρεση θα επικρατήσει. Τυλίξτε τον επικίνδυνο καθαρισμό σεevalαν χρειάζεστε να διατηρηθούν και τα δύο σήματα.Οι εξαιρέσεις διαδίδονται κανονικά. Μια εξαίρεση που εκτοξεύεται από ένα μπλοκ
deferτο οποίο εκτελείται για οποιονδήποτε άλλο λόγο (κανονική έξοδος, return, έλεγχος βρόχου) διαδίδεται στον καλούντα ακριβώς όπως κάθε άλλη εξαίρεση. Μια αποτυχημένηcloseστον καθαρισμό μπορεί έτσι να μετατρέψει μια φαινομενικά επιτυχημένη συνάρτηση σε ορατή στον καλούνταdie.Οι
$@και$!μέσα στο μπλοκ. Στη διαδρομή εξόδου που οδηγείται από εξαίρεση, η$@κρατά την εν εξελίξει εξαίρεση όσο εκτελείται το μπλοκdefer. Η ανάγνωσή της είναι αποδεκτή· η αντικατάστασή της αλλάζει αυτό που βλέπει ο καλών μετά την ολοκλήρωση του ξετυλίγματος.Λεπτότητα εμβέλειας. Η
deferσυνδέεται με το άμεσα περικλείον μπλοκ, όχι με την περικλείουσα υπορουτίνα. Μιαdeferμέσα σεif (...) { defer { ... } }εκτελείται όταν εξέρχεται το μπλοκif, όχι όταν επιστρέφει η υπορουτίνα. Τοποθετήστε τηνdeferσε εμβέλεια υπορουτίνας αν θέλετε σημασιολογία εξόδου υπορουτίνας.Πειραματική προειδοποίηση. Υπό
use warningsη πρώτη εμφάνιση προειδοποιεί. Δηλώστε σκόπιμη συμμετοχή μεno warnings 'experimental::defer'αφού δεσμευτείτε στο χαρακτηριστικό.Μη διαθέσιμη χωρίς το guard του χαρακτηριστικού. Χωρίς
use feature 'defer'(ή ένα bundle που το ενεργοποιεί), τοdefer { ... }αναλύεται ως κλήση μιας υπορουτίνας ονόματιdeferακολουθούμενη από ένα μπλοκ - σχεδόν σίγουρα όχι ό,τι εννοούσατε, και συνήθως σφάλμα κατά τη μεταγλώττιση.
Διαφορές από το upstream#
Πλήρως συμβατό με το upstream Perl 5.42. Το χαρακτηριστικό παραμένει σημασμένο ως πειραματικό στο pperl ώστε να αντικατοπτρίζει την κατηγοριοποίηση του upstream· η συμπεριφορά (προγραμματισμός κατά την εκτέλεση της δήλωσης, σειρά LIFO, διάδοση εξαιρέσεων, περιορισμοί στη διαφυγή ροής ελέγχου) ταιριάζει με την Perl 5.42.
Δείτε επίσης#
eval- το καθιερωμένο εργαλείο για τη μετατροπή εξαιρέσεων σε τιμές επιστροφής· ηdeferτο συμπληρώνει εγγυώμενη τον καθαρισμό χωρίς ρητό περιτύλιγμαevalσε κάθε διαδρομήdie- μία από τις διαδρομές εξόδου που πυροδοτεί αναβληθέντα μπλοκ· διαβάστε την μαζί με τηνdeferόταν σχεδιάζετε καθαρισμόreturn- άλλη μια διαδρομή εξόδου· μιαdeferπου προγραμματίστηκε πριν από έναreturnεκτελείται πριν συνεχίσει ο καλώνlocal- το άλλο ιδίωμα για αποκατάσταση κατάστασης με εμβέλεια· επιλέξτεlocalγια δυναμική εμβέλεια μιας μόνο μεταβλητής,deferγια αυθαίρετες ενέργειες καθαρισμού$@- η τρέχουσα εξαίρεση· ορατή μέσα σε ένα μπλοκdeferπου εκτελείται στη διαδρομή ξετυλίγματος εξαίρεσης$!- το errno συστήματος· εύκολα καταστρέφεται από κλήσεις συστήματος καθαρισμού, εφαρμόστεlocalμέσα στο αναβληθέν μπλοκ αν ο καλών χρειάζεται να διατηρηθεί