2010-08-24 4 views
18

Ich habe ein Muster, das für mehrere Member-Funktionen wiederholt, die wie folgt aussieht:Wie kann ich abstrakt ein sich wiederholendes Try-Catch-Muster in C out ++

int myClass::abstract_one(int sig1) 
{ 
    try { 
    return _original->abstract_one(sig1); 
    } catch (std::exception& err) { 
    handleException(err); 
    } catch (...) { 
    handleException(); 
    } 
} 

bool myClass::abstract_two(int sig2) 
{ 
    try { 
    return _original->abstract_two(sig2); 
    } catch (std::exception& err) { 
    handleException(err); 
    } catch (...) { 
    handleException(); 
    } 
} 

[...] 

int myClass::abstract_n(bool sig3a, short sig3b) 
{ 
    try { 
    return _original->abstract_n(sig3a, sig3b); 
    } catch (std::exception& err) { 
    handleException(err); 
    } catch (...) { 
    handleException(); 
    } 
} 

Wo abstrakt eins bis n sind Methoden einer rein virtuellen abstrakte Schnittstelle wofür myClass und _original konkrete Implementierungen sind.

Ich mag es nicht, dass sich dieses Muster im Code wiederholt und möchte einen Weg finden, das sich wiederholende Versuchs-/Fangmuster und Code als eine einzelne Abstraktion zu eliminieren, aber ich kann mir keinen guten Weg vorstellen das in C++ ohne Makros. Ich würde denken, dass es einen Weg mit Vorlagen gibt, um das besser zu machen.

Bitte schlagen Sie eine saubere Möglichkeit vor, diesen Code zu refaktorieren, um das wiederholte Muster zu abstrahieren.

+0

Makros sind wahrscheinlich Ihre beste Wette, obwohl es ein bisschen beunruhigend ist, dass Sie es oft versuchen/werfen. – GManNickG

+2

@GMan, müssen Sie es möglicherweise für eine Bibliothek, die eine C-Schnittstelle oder Callbacks mit einer C-Bibliothek registriert hat. –

+0

Haben Sie sich noch eine Lisp-Variante angesehen? Lisp ist bestrebt, strukturelle Probleme zu vermeiden. –

Antwort

21

Ich fragte eine sehr ähnliche konzeptionelle Frage, siehe Is re-throwing an exception legal in a nested 'try'?.

Grundsätzlich können Sie die verschiedenen Ausnahmehandler in eine separate Funktion verschieben, indem Sie alle Ausnahmen abfangen, den Handler aufrufen und die aktive Ausnahme erneut auslösen.

void handle() { 
try { 
    throw; 
} catch (std::exception& err) { 
    handleException(err); 
} catch (MyException& err) { 
    handleMyException(err); 
} catch (...) { 
    handleException(); 
} 
} 

try { 
    return _original->abstract_two(sig2); 
} catch (...) { 
    handle(); 
} 

Es skaliert gut mit mehr verschiedenen Ausnahmearten zu unterscheiden.Sie können die erste try .. catch(...) in Makros packen, wenn Sie mögen:

BEGIN_CATCH_HANDLER 
return _original->abstract_two(sig2); 
END_CATCH_HANDLER 
+0

+1: Ich mag diese Idee. –

+1

Ich sollte hinzufügen, dass, nachdem ich dies einmal auf SO gefragt habe, habe ich es mehrmals verwendet und ich fand es immer ziemlich elegant. Ich frage mich also, ob dieses Muster einen Namen hat? –

+4

Ich habe gehört, es nannte die "Ausnahme Dispatcher Idiom". – GManNickG

1

Ich habe keine Antwort außer, um zu empfehlen, dass Sie möglicherweise bessere Ausnahmeregelung insgesamt vermeiden und sich stattdessen auf Smart Pointers und Boost Scope Exit verlassen, um alle Ihre Ressourcen zu bereinigen. Auf diese Weise müssen Sie keine Ausnahmen abfangen, es sei denn, Sie können etwas dagegen tun, was selten der Fall ist. Dann können Sie die gesamte Ausnahmebehandlung an einer zentralen Stelle in der Anrufkette für Fehlerberichte und dergleichen durchführen.

+1

in diesem Fall sind die Ausnahmen, die ausgelöst werden, in einer 3rd-Party-Bibliothek, die eine konkrete Implementierung der abstrakten Schnittstelle bereitstellt und daher nicht vermieden werden kann. – WilliamKF

15

Eine Option, wenn es nur eine begrenzte Anzahl von Funktions arities ist, wäre eine Funktion Vorlage zu verwenden:

template <typename ReturnT, typename ClassT> 
ReturnT call_and_handle(ClassT* obj, ReturnT(ClassT::*func)()) 
{ 
    try { 
     return (obj->*func)(); 
    } 
    catch (const std::exception& ex) { 
     handleException(ex); 
    } 
    catch (...) { 
     handleException(); 
    } 
    return ReturnT(); 
} 

Dies setzt voraus, dass handleException einig Nicht-Member-Funktion ist, aber es ist einfach, es zu ändern wenn es eine Mitgliedsfunktion ist. Sie müssen entscheiden, was call_and_handle zurückgibt, wenn eine Ausnahme behandelt wird; Ich habe es eine initialisierte ReturnT als Platzhalter zurückgeben.

Dies reduziert die Mitgliederfunktionen auf:

int myClass::abstract_one() 
{ 
    return call_and_handle(_original, &myClass::abstract_one); 
} 

Sie würden eine separate Funktionsvorlage benötigen Funktionen für den Aufruf, die einen Parameter, zwei Parameter usw.

Wenn Sie Funktionen haben, die eine unhandliche haben Anzahl von Parametern und Sie waren wirklich verzweifelt, Sie konnte ein Makro verwenden (ich würde nicht wirklich empfehlen, obwohl):

#define CALL_AND_HANDLE(expr)   \ 
    try {        \ 
     return (expr);     \ 
    }         \ 
    catch (const std::exception& ex) { \ 
     handleException(ex);   \ 
    }         \ 
    catch (...) {      \ 
     handleException();    \ 
    } 

, die als verwendet werden können:

int myClass::abstract_one() 
{ 
    CALL_AND_HANDLE(_original->myClass::abstract_one()); 
} 

Als Nebenwirkung, wenn Sie catch (...) und die abgefangene Ausnahme nicht tun rethrow, sollten Sie in den meisten Fällen das Programm beenden.

+0

@James McNellis, ja die Ausnahme wird innerhalb von handleException() erneut ausgelöst. – WilliamKF

+0

@WilliamKF: In diesem Fall würde ich nur die Zeile return ReturnT() mit einem Kommentar "this line is unreachable" oder etwas in dieser Richtung kommentieren. –

+0

@ James McNellis so wird der Compiler in diesem Fall eine Warnung oder einen Fehler geben? In der kommenden C++ 0x wird es eine Möglichkeit geben, die Return-Anweisung zu vermeiden, um den Compiler glücklich zu machen? – WilliamKF

1

Verwendung boost :: function und boost :: bind. Funktioniert mit jeder Funktionssignatur, solange der Rückgabetyp übereinstimmt.

#include <boost/function.hpp> 
#include <boost/bind.hpp> 

using boost::function; 
using boost::bind; 

template<typename T> 
T exception_wrapper(boost::function<T()> func) 
{ 
    try { 
    return func(); 
    } catch (std::exception& err) { 
    handleException(err); 
    } catch (...) { 
    handleException(); 
    } 
} 

// ways to call 
int result = exception_wrapper<int>(bind(libraryFunc, firstParam)); 
// or a member function 
LibraryClass* object; 
result = exception_wrapper<int>(bind(&LibraryClass::Function, object, firstParam)); 

// So your wrapping class: 
class YourWrapper : public SomeInterface 
{ 
public: 
    int abstract_one(int sig1) 
    { 
     return exception_wrapper<int>(bind(&LibraryClass::concrete_one, m_pObject, sig1)); 
    } 

    bool abstract_two(int sig1, int sig2) 
    { 
     return exception_wrapper<bool>(bind(&LibraryClass::concrete_two, m_pObject, sig1, sig2)); 
    } 

    // ... 

private: 
    LibraryClass* m_pObject; 
}; 
+0

Sie vermissen eine Rückkehr in exception_wrapper in der Fall, dass func() wirft. – uckelman

3

Als eine Variante auf Alexander Gesslers Lösung können Sie einige der Klammern weglassen, die diese Implementierung ein wenig lang machen. Es macht genau dasselbe, nur mit etwas weniger {} verbage.

void handle() try 
{ 
    throw; 
} 
    catch (std::exception& err) 
{ 
    handleException(err); 
} 
    catch (MyException& err) 
{ 
    handleMyException(err); 
} 
    catch (...) 
{ 
    handleException(); 
} 



int myClass::abstract_one(int sig1) try 
{ 
    return _original->abstract_one(sig1); 
} 
    catch (...) 
{ 
    handle(); 
    return -1; 
} 
+0

Interessant, ich habe noch nie gesehen, den äußersten Ausdruck eines Funktionskörpers zu versuchen, das ist Standardsyntax? – WilliamKF

+2

@WilliamKF Es heißt ein "Funktionstryblock" und ist im 15.1.4-Abschnitt meiner Kopie des Normentwurfs [N1905 = 05-0165] von 2005 leicht beschrieben. –

+0

Was macht der Compiler dann, wenn Sie den Catch beenden? Blockieren ohne werfen, was ist der Rückgabewert? – WilliamKF

2

Meine Antwort ist vom Konzept her ähnlich wie James McNellis', mit der Ausnahme, dass ich boost :: bind, das schwere Heben zu tun:

using boost::bind; 

class myClass 
{ 
public: 
    myClass(origClass * orig) : orig_(orig) {} 

    int f1(bool b) { return wrapper(bind(&origClass::f1, orig_, b)); } 
    bool f2(int i) { return wrapper(bind(&origClass::f2, orig_, i)); } 
    void f3(int i) { return wrapper(bind(&origClass::f3, orig_, i)); } 
private: 
    origClass * orig_; 

    template <typename T> 
    typename T::result_type wrapper(T func) 
    { 
    try { 
     return func(); 
    } 
    catch (std::exception const &e) { 
     handleError(e); 
    } 
    catch (...) { 
     handleError(); 
    } 
    } 
}; 

Bitte beachte, dass ich nicht einen Schub verwenden würde :: function hier, da es das Inlining stören kann.

1

Meine Antwort ist: nichts tun. Das erste Codebeispiel wie gezeigt ist in Ordnung. Also, was gibt es Wiederholung? Es ist klar und deutlich und tut, wie es aussieht. Es kann ohne zusätzliche mentale Belastungen verstanden werden, die über den Code hinausgehen und allgemeine Kenntnisse von C++ haben.

Betrachten Sie Ihr Motiv für das Stellen dieser Frage.

Wo ich herkomme, sind vergangene Projekte, in denen Code von anderen untersucht werden musste - nicht PhD Comp Sci Experten, sondern Bundesinspektoren, Maschinenbauingenieure, Autodidakten Programmierer, Wissenschaftler, etc. Kluge Leute, alle von ihnen (oder die meisten von ihnen), aber nur ein Chrom-Dome PhD, oder ein jüngerer Programmierer, der jeden mit seinem hohen IQ beeindrucken würde, würde die cleveren "Lösungen" für diese Frage schätzen. Für die Orte, die ich gewesen bin, schlägt nichts klarer klarer Code, der das tut, was er sagt, ohne die mentale Belastung, die Bedeutung von Dutzenden von Klassen, Makros usw. zu beachten und "Designmuster" zu erkennen, die nur richtig verstanden werden erfahrene Softwareentwickler.

In zunehmendem Maße finde ich, dass C++ (und Java und C#) Code in einem codierenden Sinn ausgefeilter wird, aber von Nicht-Experten in C++ verstanden werden muss.

Natürlich YMMV, abhängig von der Zielgruppe und Quelle der potenziellen zukünftigen Wartungsprogrammierer. Manchmal ist eine kluge Programmierung notwendig, um bestimmte Ziele zu erreichen. Ist dein Fall ein Beispiel?

+0

Ich glaube, die akzeptierte Lösung ist ein vernünftiges Gleichgewicht zwischen Verschleierung und reduziertem Code zu erhalten und zu verstehen. – WilliamKF

+0

Was ist, wenn es Wiederholungen gibt? Wenn ich also die Codierungsstandards meines Kunden befolge, habe ich Dutzende von Instanzen ähnlichen Codes, die es schwer haben, zu warten und einen größeren Schmerz beim Lesen zu haben! Das ist, was ... – sage

+0

Wenn Code von Nicht-Experten verstanden werden muss, müssen sie einen Experten engagieren, um es für sie zu interpretieren. Wenn Sie über das Reduzieren von 7-8 Zeilen pro Funktion in einem Projekt in MB von nur Code mit dem Wiederholen von Strings bis zu 2 Zeilen mit einer einzelnen Instanz einer Zeichenfolge (z.Hinzufügen einer Nachricht zusammen mit dem Wurf) gibt es wirklich keine Wahl getroffen werden und wenn jemand kann nicht verstehen, dass es weil sie nicht ein Programmierer sind, mit zu beginnen. Der Versuch, den Code lesbar für Menschen zu machen, ist, wie Sie mit VB enden (und es funktioniert sowieso nicht, macht die Dinge für Programmierer nur stressiger.) – CoryG

Verwandte Themen