2010-05-02 10 views
5

Próbuję wybrać między dwoma sposobami tworzenia wystąpienia obiektu & z obsługą wyjątków konstruktora dla obiektu, który jest krytyczny dla mojego programu, tj. Jeśli konstrukcja nie powiedzie się, program nie może kontynuować .Dobry styl postępowania z uszkodzeniem konstruktora krytycznego obiektu

Mam klasę SimpleMIDIOut, która otacza podstawowe funkcje Win32 MIDI. Otworzy ono urządzenie MIDI w konstruktorze i zamknie je w destruktorze. Przekaże wyjątek odziedziczony po std :: exception w konstruktorze, jeśli nie można otworzyć urządzenia MIDI.

Które z poniższych sposobów łapania wyjątków konstruktora dla tego obiektu byłoby bardziej zgodne z najlepszymi praktykami C++

metoda 1 - Stack przydzielonego obiektu, tylko w zakresie wewnątrz bloku try

#include <iostream> 
#include "simplemidiout.h" 

int main() 
{ 
    try 
    { 
     SimpleMIDIOut myOut; //constructor will throw if MIDI device cannot be opened 
     myOut.PlayNote(60,100); 

     //..... 
     //myOut goes out of scope outside this block 
     //so basically the whole program has to be inside 
     //this block. 
     //On the plus side, it's on the stack so 
     //destructor that handles object cleanup 
     //is called automatically, more inline with RAII idiom? 
    } 
    catch(const std::exception& e) 
    { 
     std::cout << e.what() << std::endl; 
     std::cin.ignore(); 
     return 1; 
    } 

    std::cin.ignore(); 
    return 0; 
} 

Metoda 2 - Wskaźnik do obiektu, przydzielone sterty, ładniejszy kod strukturalny?

#include <iostream> 
#include "simplemidiout.h" 

int main() 
{ 
    SimpleMIDIOut *myOut; 

    try 
    { 
     myOut = new SimpleMIDIOut(); 
    } 
    catch(const std::exception& e) 
    { 
     std::cout << e.what() << std::endl; 
     delete myOut; 
     return 1; 
    } 

    myOut->PlayNote(60,100); 

    std::cin.ignore(); 

    delete myOut; 
    return 0; 

} 

lubię wygląd kodu w metodzie 2 lepiej, nie trzeba zaciąć cały mój program w bloku try, ale Metoda 1 tworzy obiekt na stosie tak C++ zarządza czas życia obiektu, co jest bardziej zgodne z filozofią RAII, nieprawdaż?

Nadal jestem nowicjuszem, więc wszelkie opinie na temat powyższego są mile widziane. Jeśli istnieje jeszcze lepszy sposób sprawdzenia/poradzenia sobie z awarią konstruktora w takiej sytuacji, proszę dać mi znać.

+0

+1 za dobrze zadane pierwsze pytanie. – razlebe

Odpowiedz

2

Osobiście wolę pierwszy styl, którego używałeś - Metoda 1 - przypisanie obiektu SimpleMIDIOut jako lokalnego do zakresu bloku try-catch.

  • Dla mnie jedną z korzyści płynących z bloku try-catch jest to, że dostarcza czysty, schludny miejsce dla tego błędu obsługi kodu - blok catch - który pozwala określić logikę biznesową w jednym Nicei czytelny, nieprzerwany przepływ.

  • drugie, blok try-catch masz określony jest wystarczająco rodzajowy do czynienia z żadnym wyjątkiem, że wywodzi się z std::exception - tak mający większość kodu programu w bloku try-catch nie jest złą rzeczą. Zapobiegnie to niespodziewanemu przerwaniu działania programu, jeśli wystąpi najgorsze i zostanie zgłoszony wyjątek. Potencjalnie może to być wyjątek, którego się nie spodziewasz, jak indeks poza zakresem lub wyjątek alokacji pamięci.

  • Jeśli nie podoba ci się posiadanie mnóstwa kodu w bloku try-catch, ze względu na czytelność, jest to w porządku, ponieważ jest to dobra praktyka projektowa dla refactor big lumps of code into functions. W ten sposób można zachować samą główną funkcję na minimalnej liczbie linii.

Patrząc na Metoda 2:

  • Nie trzeba, że ​​delete w bloku catch. Jeśli podczas budowy został zgłoszony wyjątek, nie ma tam żadnego obiektu do usunięcia.

  • Czy zastanawiałeś się, w jaki sposób zamierzasz obsłużyć dowolny numer std::exception, który może zostać zgłoszony przez myOut->PlayNote?Jest poza zasięgiem twojego try-catch, więc wyjątek tutaj zabiłby program niespodziewanie. Właśnie to robiłem z moją drugą kulą, powyżej.

Jeśli było zdecydować zawinąć większość programu w tym bloku try-catch, ale mimo to chcielibyśmy, aby dynamicznie przydzielać obiekt SimpleMIDIOut, można dokonać zarządzanie pamięcią nieco łatwiejsze przy użyciu auto_ptr do zarządzać pamięci dla Ciebie w przypadku wyjątek:

try 
{ 
    std::auto_ptr<SimpleMIDIOut> myOut(new SimpleMIDIOut()); 
    myOut->PlayNote(60,100); 
    std::cin.ignore(); 
} // myOut goes out of scope, SimpleMIDIOut object deleted 
catch(const std::exception& e) 
{ 
    std::cout << e.what() << std::endl; 
    return 1; 
} 


return 0; 

... ale może równie dobrze można po prostu utworzyć obiekt SimpleMIDIOut jako lokalny zamiast dynamiczne.

+0

Dzięki za szczegółową odpowiedź. Jedzenie dla myśli. Zaklinowanie tego wszystkiego do tego bloku try/catch wydaje mi się dziwne na poziomie estetycznym i chciałem trochę sprawdzić poprawność tej struktury kodu. – MTLPhil

+0

Może to zabrzmieć dziwnie, ale tak naprawdę bardzo dobrą praktyką jest zamknięcie kodu w 'main' w bloku try/catch, aby zapobiec nieoczekiwanemu zakończeniu (i zapisać wyjątek do analizy). Powinienem jednak zauważyć, że jest to rodzaj desperackiej miary i mającej zastosowanie wyłącznie do funkcji "głównych" i głównych, nie należy przyzwyczajać do zamykania każdej funkcji w try/catch;) –

1

Czy możesz wyjaśnić, czy masz kontrolę nad kodem źródłowym w SimpleMIDIOut?

Jeśli to zrobisz, klasa ta nie powinna rzucać wyjątku od CTOR. Kod w CTOR tej klasy powinien być zawarty w bloku try \ catch.

Jeśli tego nie zrobisz, skorzystam z metody 1 dla jasności - fakt, że cały program będzie musiał być w tym bloku try, może zostać rozwiązany poprzez refaktoryzację na małe metody.

+1

To jest całkowicie poprawne, aby rzucić wyjątek od konstruktora, aby wskazać dzwoniącemu, że z jakiegokolwiek powodu nie można ukończyć budowy. – razlebe

+0

To jest ważne - ale moim zdaniem jest to zły projekt, który może prowadzić do wyjątków czasu pracy. –

+0

@drelihan Alternatywą jest zezwolenie na konstrukcję obiektu w stanie nieużytecznym, a następnie czekanie na jego użycie. Znacznie gorszy wybór wzorów IMHO. – razlebe

Powiązane problemy