Zadaniem GotW#8 jest zaimplementowanie w strukturze C++ struktury generycznej stosu wyjątków, przy założeniu, że destruktor argumentu szablonu nie zostanie wygenerowany. Sztuką jest obsługa potencjalnie rzucania operacjami z argumentami szablonu (konstruktor, konstruktor kopiowania, przypisanie) w taki sposób, aby pozostawić stos w spójnym stanie, jeśli rzucają.Technika obsługi wyjątków w klasie podstawowej
W rozwiązaniu, Herb Sutter mówi
Aby utrzymać to rozwiązanie prostsze, zdecydowałem nie zademonstrować techniki klasą bazową dla własności zasobów wyjątku bezpieczny.
Po pewnym googling znalazłem this answer przez Dave Abrahams sięga roku 1997. W jego rozwiązanie obsługuje on przydział i kasowanie pamięci w klasie bazowej i realizuje operacje stosu w podklasie. W ten sposób zapewnia, że kopiowanie elementów w konstruktorze kopiowania jest oddzielane od alokacji pamięci, tak więc jeśli kopiowanie się nie powiedzie, wywoływany jest destruktor klasy podstawowej, bez względu na wszystko.
Dla porównania, tutaj jest kopia konstruktor Dave'a z moim komentarzu dodaje:
// v_ refers to the internal array storing the stack elements
Stack(const Stack& rhs)
: StackBase<T>(rhs.Count()) // constructor allocates enough space
// destructor calls delete[] appropriately
{
while (Count() < rhs.Count())
Push(rhs.v_[ Count() ]); // may throw
}
Jeżeli konstruktor baza powiedzie, pamięć clean-up w bazowej destructor jest gwarantowana nawet jeśli kopia konstruktor podklasy rzuca.
Moje pytania są następujące:
- Czy istnieją inne korzyści z podejściem, z wyjątkiem przypadków opisanych powyżej?
wymyśliłem tej kopii konstruktora kiedy rozwiązać problem na własną rękę:
// v_ refers to the internal array storing the stack elements // vsize_ is the amount of space allocated in v_ // vused_ is the amount of space used so far in v_ Stack (const Stack &rhs) : vsize_ (0), vused_ (0), v_ (0) { Stack temp (rhs.vused_); // constructor calls `new T[num_elements]` // destructor calls `delete[] v_` std::copy (rhs.v_, rhs.v_ + rhs.vused_, temp.v_); // may throw swap (temp); } void swap (Stack &rhs) { std::swap (v_, rhs.v_); std::swap (vused_, rhs.vused_); std::swap (vsize_, rhs.vsize_); }
Uważam to nieco kłopotliwe, aby mieć klasę bazową w porównaniu do tego podejścia. Czy istnieje jakikolwiek powód, dla którego technika podstawowa powinna być preferowana w porównaniu z tym podejściem polegającym na kopiowaniu, kopiowaniu i zamianie? Zauważ, że zarówno Dave, jak i ja mamy już członka
swap()
, ponieważ używamy go w naszymoperator=()
.- Technika Dave'a Abrahamsa nie wydaje się być dobrze znana (według Google). Czy ma inną nazwę, czy to standardowa praktyka, czy coś przeoczyłem?
Uwagi:
- zakładać Dave
Push()
w pętli za równoważne do mojego użytkowaniastd::copy
- trzymajmy inteligentne kursory z odpowiedzią, a ich wykorzystanie byłoby zabrać punkt zarządzania pamięć jawnie w tym ćwiczeniu
Udostępnianie bazy wydaje się być jedynym argumentem przemawiającym za podejściem bazowym. Dam mu dzień lub dwa, żeby sprawdzić, czy ktoś inny nie przedstawi kolejnego argumentu. – Irfy
Podejście klasy bazowej wygrywa, gdy zawartość obiektu jest ciężka, a zamiana na 'temp' jest droga. (Chociaż "ruch" C++ 11 może pomóc) Możesz dodać ten bit do swojej odpowiedzi, jeśli chcesz. – Irfy