Ροή ελέγχου

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 εκτός του μπλοκ,

  • έλεγχος βρόχου μέσω next, last ή redo.

Αν η ροή εκτέλεσης δεν φτάσει ποτέ στην ίδια τη δήλωση 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 μέσα στο αναβληθέν μπλοκ αν ο καλών χρειάζεται να διατηρηθεί