Qual è la differenza concettuale tra finalmente e un distruttore?

12

In primo luogo, sono a conoscenza di Perché non c'è 'finalmente' costruisci in C ++? ma una lunga discussione di commenti su un'altra domanda sembra giustificare una domanda separata.

A parte il problema che finally in C # e Java possono fondamentalmente esistere solo una volta (== 1) per ambito e un singolo ambito può avere più (== n) distruttori C ++, penso che siano essenzialmente gli stessi cosa. (Con alcune differenze tecniche.)

Tuttavia, un altro utente ha sostenuto :

... I was trying to say that a dtor is inherently a tool for (Release sematics) and finally is inherently a tool for (Commit semantics). If you don't see why: consider why it's legitimate to throw exceptions on top of each other in finally blocks, and why the same is not for destructors. (In some sense, it's a data vs. control thing. Destructors are for releasing data, finally is for releasing control. They are different; it's unfortunate that C++ ties them together.)

Qualcuno può chiarirlo?

    
posta Martin Ba 02.12.2015 - 10:28
fonte

5 risposte

6
  • Transazione ( try )
  • Output / risposta errore ( catch )
  • Errore esterno ( throw )
  • Errore programmatore ( assert )
  • Rollback (la cosa più vicina potrebbe essere la guardia del campo in linguaggi che li supportano in modo nativo)
  • Risorse di rilascio (distruttori)
  • Flusso di controllo indipendente dalla transazione Misc ( finally )

Impossibile trovare una descrizione migliore per finally rispetto al flusso di controllo indipendente dalla transazione. Non è necessariamente mappato così direttamente a qualsiasi concetto di alto livello nel contesto di una transazione e di una mentalità di recupero degli errori, specialmente in un linguaggio teorico che ha sia distruttori che finally .

Ciò che mi manca di più è una funzionalità linguistica che rappresenta direttamente il concetto di ripristino degli effetti collaterali esterni. Le protezioni di ambito in lingue come D sono la cosa più vicina che riesco a pensare che si avvicini a rappresentare quel concetto. Dal punto di vista del flusso di controllo, un rollback nell'ambito di una particolare funzione dovrebbe distinguere un percorso eccezionale da uno normale, automatizzando contemporaneamente il rollback implicitamente di eventuali effetti collaterali causati dalla funzione in caso di fallimento della transazione, ma non quando la transazione ha esito positivo . Questo è abbastanza facile da fare con i distruttori se, per esempio, impostiamo un valore booleano da valutare come succeeded su true alla fine del nostro blocco try per impedire la logica di rollback in un distruttore. Ma è un modo abbastanza indiretto per farlo.

Anche se potrebbe sembrare che non salverebbe così tanto, l'inversione dell'effetto collaterale è una delle cose più difficili da ottenere (es: cosa rende così difficile scrivere un contenitore generico eccezionalmente sicuro).

    
risposta data 18.12.2015 - 12:44
fonte
4

In un certo senso lo sono - nello stesso modo in cui una Ferrari e un transito possono essere usati entrambi per stroncare i negozi per una pinta di latte, anche se sono progettati per usi diversi.

Potresti piazzare un tentativo / finalmente costruire in ogni ambito e ripulire tutte le variabili definite dall'ambito nel blocco finally per emulare un distruttore C ++. Questo, concettualmente, è ciò che fa C ++: il compilatore chiama automaticamente il distruttore quando una variabile esce dall'ambito (cioè alla fine del blocco di scope). Dovresti organizzare la tua prova / finalmente così la prova è la prima cosa e infine l'ultima cosa in ogni ambito. Dovresti anche definire uno standard per ogni oggetto per avere un metodo con un nome specifico che usa per ripulire il suo stato che chiameresti nel blocco finally, anche se penso che potresti lasciare la normale gestione della memoria che la tua lingua fornisce ripulisci l'oggetto svuotato quando vuole.

Tuttavia, non sarebbe bello farlo, e anche se .NET ha introdotto IDispose come distruttore gestito manualmente e utilizzando i blocchi come un tentativo per rendere la gestione manuale leggermente più semplice, non è ancora qualcosa che si vorrebbe fai in pratica.

    
risposta data 02.12.2015 - 12:13
fonte
4

Dal mio punto di vista la differenza principale è che un destructor in c ++ è un implicito meccanismo (invocato automaticamente) per il rilascio di risorse allocate mentre il try ... finalmente può essere usato come meccanismo esplicito per farlo.

Nei programmi c ++ il programmatore è responsabile del rilascio delle risorse allocate. Questo di solito è implementato nel distruttore di una classe e fatto immediatamente quando a variabile va fuori portata o o quando viene chiamato delete.

Quando in c ++ viene creata una variabile locale di una classe senza utilizzare new delle risorse di tali istanze sono liberate implicite dal distruttore quando c'è un'eccezione.

// c++
void test() {
    MyClass myClass(someParameter);
    // if there is an exception the destructor of MyClass is called automatically
    // this does not work with
    // MyClass* pMyClass = new MyClass(someParameter);

} // on test() exit the destructor of myClass is implicitly called

In java, c # e altri sistemi con gestione automatica della memoria il sistema garbage collector decide quando viene distrutta un'istanza di classe.

// c#
void test() {
    MyClass myClass = new MyClass(someParameter);
    // if there is an exception myClass is NOT destroyed so there may be memory/resource leakes

    myClass.destroy(); // this is never called
}

Non esiste un meccanismo implicito a questo, quindi devi programmarlo esplicitamente usando try finalmente

// c#
void test() {
    MyClass myClass = null;

    try {
        myClass = new MyClass(someParameter);
        ...
    } finally {
        // explicit memory management
        // even if there is an exception myClass resources are freed
        myClass.destroy();
    }

    myClass.destroy(); // this is never called
}
    
risposta data 02.12.2015 - 12:29
fonte
3

Sono contento che tu abbia postato questa domanda come una domanda. :)

Stavo cercando di dire che i distruttori e finally sono concettualmente diversi:

  • I distruttori servono per il rilascio di risorse ( dati )
  • finally serve per tornare al chiamante ( controllo )

Considera, per esempio, questo ipotetico pseudo-codice:

try {
    bar();
} finally {
    logfile.print("bar has exited...");
}

finally qui sta risolvendo interamente un problema di controllo e non un problema di gestione delle risorse.
Non avrebbe senso farlo in un distruttore per una serie di motivi:

  • Nessuna cosa viene "acquisita" o "creata"
  • La mancata stampa sul file di log non causerà perdite di risorse, danneggiamento dei dati, ecc. (supponendo che il file di registro qui non venga reimmesso nel programma altrove)
  • È legittimo che logfile.print fallisca, mentre destruction (concettualmente) non può fallire

Ecco un altro esempio, questa volta come in Javascript:

var mo_document = document, mo;
function observe(mutations) {
    mo.disconnect();  // stop observing changes to prevent re-entrance
    try {
        /* modify stuff */
    } finally {
        mo.observe(mo_document);  // continue observing (conceptually, this can fail)
    }
}
mo = new MutationObserver(observe);
return observe();

Nell'esempio precedente, ancora una volta, non ci sono risorse da rilasciare.
Infatti, il blocco finally è acquisire risorse internamente per raggiungere il suo obiettivo, che potrebbe potenzialmente fallire. Quindi, non ha senso usare un distruttore (se Javascript ne ha uno).

D'altra parte, in questo esempio:

b = get_data();
try {
    a.write(b);
} finally {
    free(b);
}

finally sta distruggendo una risorsa, b . È un problema di dati. Il problema non riguarda il ritorno pulito del controllo al chiamante, ma piuttosto l'eliminazione delle perdite di risorse.
Il fallimento non è un'opzione e dovrebbe (concettualmente) non accadere mai.
Ogni rilascio di b è necessariamente associato a un'acquisizione e ha senso utilizzare RAII.

In altre parole, solo perché è possibile utilizzare sia per simulare che non significa che entrambi sono lo stesso problema o che entrambi sono soluzioni appropriate per entrambi i problemi.

    
risposta data 02.12.2015 - 11:16
fonte
1

La risposta di k3b lo esprime davvero bene:

a destructor in c++ is an implicit mechanism (automatically invoked) for releasing allocated resources while the try ... finally can be used as an explicit mechanism to do that.

Per quanto riguarda le "risorse", mi piace fare riferimento a Jon Kalb: RAII dovrebbe significare che l'acquisizione della responsabilità è l'inizializzazione .

In ogni caso, per quanto riguarda implicito o esplicito, questo sembra essere proprio questo:

  • A tor è uno strumento per definire quali operazioni devono avvenire - implicitamente - quando termina la vita di un oggetto (che spesso coincide con la fine dello scope)
  • Un blocco finally è uno strumento per definire - esplicitamente - quali operazioni devono avvenire alla fine dello scope.
  • Inoltre, tecnicamente, ti è sempre consentito lanciare definitivamente, ma vedi sotto.

Penso che sia per la parte concettuale, ...

... ora ci sono alcuni dettagli interessanti su IMHO:

Inoltre non penso che c'tor / d'tor debbano concettualmente "acquisire" o "creare" qualcosa, a parte la responsabilità di eseguire del codice nel distruttore. Questo è ciò che finalmente fa anche: esegui del codice.

E anche se il codice in un blocco finally può certamente generare un'eccezione, non è abbastanza per me distinguere che sono concettualmente differenti sopra espliciti o impliciti.

(Inoltre, non sono affatto convinto che il "buon" codice dovrebbe essere finalmente lanciato - forse questa è un'altra domanda a sé stessa.)

    
risposta data 02.12.2015 - 20:36
fonte

Leggi altre domande sui tag