Przeciążanie destruktorów

2007-09-15 11:41

Trochę poprzednio ponarzekałem na technikę RAII stosowaną w C++, a raczej na towarzyszący jej brak wygodnej instrukcji finally. Było to być może odrobinę niesprawiedliwe, gdyż mechanizm ma dość duże możliwości – przynajmniej potencjalnie :)

Natrafiłem jakiś czas temu na Usenecie na ciekawy pomysł związany z tą właśnie techniką. Chodzi tu o wykrywanie, z jakiego powodu obiekt lokalny ma zostać zniszczony. Może się to bowiem odbyć w normalny sposób (gdy kończy się wykonanie odpowiedniego bloku kodu i program przechodzi dalej) lub w wyniku odwijania stosu podczas obsługi wyjątku. W obu przypadkach w C++ jest jednak wywoływany jeden i ten sam destruktor.
Jest to w porządku, jeżeli jego zadaniem jest tylko zwolnienie zasobu (czyli np. zamknięcie otwartego deskryptora pliku). Możemy sobie aczkolwiek wyobrazić zastosowanie RAII do tzw. transakcji:

  1. try
  2. {
  3.    CTransaction ts;
  4.    ts.DoSomething();  // jakaś operacja
  5.  
  6.    // ...
  7.    if (SomethingBadHappened())
  8.       throw std::exception("Failure!");
  9. }
  10. catch (...) { /* ... */ }

Transakcja to termin znany głównie programistom zajmującym się bazami danych lub innymi pokrewnymi dziedzinami “sortowania ogórków” ;) W skrócie, jest to taki ciąg operacji, który musi być zaaplikowany albo w całości, albo wcale. Jeżeli po drodze zdarzy się coś nieoczekiwanego i transakcję trzeba przerwać, wszystkie wykonane do tej chwili operacji powinny zostać odwrócone (rollback).
Można by to zrobić automatycznie w reakcji na rzucenie wyjątku, gdyby C++ pozwalał na wykrycie wspomnianych dwóch sposobów niszczenia obiektu. Ciekawym pomysłem na to jest dopuszczenie więcej niż jednego destruktora:

  1. class CTransaction
  2. {
  3.    public:
  4.       CTransaction() { Begin(); }
  5.       ~CTransaction() { Commit(); }
  6.       ~CTransaction(const std::exception&) { Rollback(); }
  7. };

Zwykły, bezparametrowy, byłby wywoływany w przypadku zwyczajnego opuszczenia bloku kodu. Natomiast destruktor przyjmujący parametr włączałby się wówczas, gdy niszczenie obiektu zdarzy się z powodu wyjątku. Taki destruktor “łapałby” więc na chwilę taki wyjątek – lecz nie po to, by go obsłużyć, ale wyłącznie w celu odpowiedniego zakończenia życia obiektu w sytuacji kryzysowej. Parametr takiego destruktora odpowiadałby typowi wyjątku, który ten destruktor miałby “łapać”.

Obecnie nie jest naturalnie możliwe stosowanie w C++ takiej konstrukcji. Istnieje jednak sposób na sprawdzenie, czy jesteśmy właśnie w trakcie obsługi jakiegoś wyjątku. Służy do tego mało znana funkcja uncaught_exception z przestrzeni std (nagłówek exception):

  1. CTransaction::~CTransaction()
  2. {
  3.    if (std::uncaught_exception())
  4.       // destruktor wywołany przez wyjątek - odwracamy transakcję
  5.       Rollback();
  6.    else
  7.       // normalne wywołanie destruktora - zatwierdzamy
  8.       Commit();
  9. }

Wprawdzie nie zapewnia ona dostępu do samego obiektu wyjątku (ani poznania jego typu), ale pozwala na zorientowanie się, czy taki wyjątek w ogóle wystąpił. A to, jak widać, najczęściej wystarczy. Tak więc chociaż przeciążanie destruktorów na pierwszy rzut oka brzmi interesująco (i intrygująco), nie jest, jak sądzę, zbytnio potrzebne.

Tags: , , ,
Author: Xion, posted under Programming »


2 comments for post “Przeciążanie destruktorów”.
  1. marioosz:
    March 13th, 2008 o 15:20

    nie jest to dobra metoda bo: http://www.gotw.ca/gotw/047.htm

  2. Xion:
    March 13th, 2008 o 15:40

    Rzeczywiście uncaught_excpetion() nie zwraca dokładnie tego, czego byśmy chcieli. Jej rezultat powinien być zasadniczo zależny od miejsca wywołania, czyli w tym przypadku powinien informować, czy obiekt spod this jest niszczony w wyniku jakiegoś odwijania stosu, czy też normalnego wyjścia z bloku (jeśli oczywiście jest lokalny).

    W sumie więc przeciążanie destruktorów może być przydatne… chyba że ktoś w końcu pójdzie po rozum do głowy i pomyśli o finally, które to rozwiązuje i ten problem, i jeszcze parę innych :)

Comments are disabled.
 


© 2019 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.